BOOKS FOR PROFESSIONALS BY PROFESSIONALS ® Friesen RELATED Beginning Java 7 Get coding with Beginning Java 7. This definitive guide to Oracle’s latest release of the popular Java language and platform details the many APIs and tools that you’ll need to master to become an accomplished Java developer. Author Jeff Friesen first gives you a comprehensive guided tour of the Java language and shows you how to start programming with the JDK and NetBeans. He then takes you through all the major APIs, from math to concurrency by way of wrappers, reference, reflection, string handling, threading, and collections. Next, he explains how to build graphical user interfaces; tells you everything you need to know about interacting with filesystems, networks, and databases; and details parsing, creating, and transforming XML documents as well as working with web services. You’ll even see how Java extends to Android, from architecture to development tools. With Beginning Java 7, you’ll learn: • The entire Java language, including new Java 7 features such as switch on string, try-with-resources, final rethrow, multicatch, and SafeVarargs • A huge assortment of APIs, including Java 7-specific APIs such as the Fork/Join Framework, Objects, JLayer, and NIO.2 • Essential Java 7 tools, starting with the javac compiler and java application launcher • How to develop Android apps Each chapter features exercises that help you test what you learned along the way. In addition, the book walks you through the development of a simple application, giving you essential first-hand experience and practical tips that will aid you in all your future Java 7 projects. Shelve in Programming Languages / Java User level: Beginning–Intermediate SOURCE CODE ONLINE www.apress.com This book was purchased by [email protected] For your convenience Apress has placed some of the front matter material after the index. Please use the Bookmarks and Contents at a Glance links to access them. Contents at a Glance About the Author.................................................................................................. xiv About the Technical Reviewer .............................................................................. xv Acknowledgments ............................................................................................... xvi Introduction ........................................................................................................ xvii Chapter 1: Getting Started with Java......................................................................1 Chapter 2: Discovering Classes and Objects ........................................................51 Chapter 3: Exploring Advanced Language Features ...........................................131 Chapter 4: Touring Language APIs .....................................................................227 Chapter 5: Collecting Objects..............................................................................319 Chapter 6: Touring Additional Utility APIs ..........................................................401 Chapter 7: Creating and Enriching Graphical User Interfaces ............................435 Chapter 8: Interacting with Filesystems.............................................................511 Chapter 9: Interacting with Networks and Databases ........................................585 Chapter 10: Parsing, Creating, and Transforming XML Documents ...................663 Chapter 11: Working with Web Services ............................................................751 Chapter 12: Java 7 Meets Android .....................................................................831 Index ...................................................................................................................873 iii CHAPTER 1 Getting Started with Java Welcome to Java. This chapter launches you on a tour of this technology by focusing on fundamentals. First, you receive an answer to the “What is Java?” question. If you have not previously encountered Java, the answer might surprise you. Next, you are introduced to some basic tools that will help you start developing Java programs, and to the NetBeans integrated development environment, which simplifies the development of these programs. Finally, you explore fundamental language features. What Is Java? Java is a language for describing programs, and Java is a platform on which to run programs written in Java and other languages (e.g., Groovy, Jython, and JRuby). This section introduces you to Java the language and Java the platform. Note To discover Java’s history, check out Wikipedia’s “Java (programming language)” (http://en.wikipedia.org/wiki/Java_(programming_language)#History) and “Java (software platform)” (http://en.wikipedia.org/wiki/Java_(software_platform)#History) entries. Java Is a Language Java is a general-purpose, class-based, and object-oriented language patterned after C and C++ to make it easier for existing C/C++ developers to migrate to this language. Not surprisingly, Java borrows elements from these languages. The following list identifies some of these elements: • Java supports the same single-line and multiline comment styles as found in C/C++ for documenting source code. • Java provides the if, switch, while, for, and other reserved words as found in the C and C++ languages. Java also provides the try, catch, class, private, and other reserved words that are found in C++ but not in C. • As with C and C++, Java supports character, integer, and other primitive types. Furthermore, Java shares the same reserved words for naming these types; for example, char (for character) and int (for integer). 1 CHAPTER 1 GETTING STARTED WITH JAVA • Java supports many of the same operators as C/C++: the arithmetic operators (+, -, *, /, and %) and conditional operator (?:) are examples. • Java also supports the use of brace characters { and } to delimit blocks of statements. Although Java is similar to C and C++, it also differs in many respects. The following list itemizes some of these differences: • Java supports an additional comment style known as Javadoc. • Java provides transient, synchronized, strictfp, and other reserved words not found in C or C++. • Java’s character type has a larger size than the version of this type found in C and C++, Java’s integer types do not include unsigned variants of these types (Java has no equivalent of the C/C++ unsigned long integer type, for example), and Java’s primitive types have guaranteed sizes, whereas no guarantees are made for the equivalent C/C++ types. • Java doesn’t support all of the C/C++ operators. For example, there is no sizeof operator. Also, Java provides some operators not found in C/C++. For example, >>> (unsigned right shift) and instanceof are exclusive to Java. • Java provides labeled break and continue statements. These variants of the C/C++ break and continue statements provide a safer alternative to C/C++’s goto statement, which Java doesn’t support. Note Comments, reserved words, types, operators, and statements are examples of fundamental language features, which are discussed later in this chapter. A Java program starts out as source code that conforms to Java syntax, rules for combining symbols into meaningful entities. The Java compiler translates the source code stored in files that have the “.java” file extension into equivalent executable code, known as bytecode, which it stores in files that have the “.class” file extension. Note The files that store compiled Java code are known as classfiles because they often store the runtime representation of Java classes, a language feature discussed in Chapter 2. The Java language was designed with portability in mind. Ideally, Java developers write a Java program’s source code once, compile this source code into bytecode once, and run the bytecode on any platform (e.g., Windows, Linux, and Mac OS X) where Java is supported, without ever having to change the source code and recompile. Portability is achieved in part by ensuring that primitive types have the same sizes across platforms. For example, the size of Java’s integer type is always 32 bits. 2 CHAPTER 1 GETTING STARTED WITH JAVA The Java language was also designed with robustness in mind. Java programs should be less vulnerable to crashes than their C/C++ counterparts. Java achieves robustness in part by not implementing certain C/C++ features that can make programs less robust. For example, pointers (variables that store the addresses of other variables) increase the likelihood of program crashes, which is why Java doesn’t support this C/C++ feature. Java Is a Platform Java is a platform that executes Java-based programs. Unlike platforms with physical processors (e.g., an Intel processor) and operating systems (e.g., Windows 7), the Java platform consists of a virtual machine and execution environment. A virtual machine is a software-based processor with its own set of instructions. The Java Virtual Machine (JVM)’s associated execution environment consists of a huge library of prebuilt functionality, commonly known as the standard class library, that Java programs can use to perform routine tasks (e.g., open a file and read its contents). The execution environment also consists of “glue” code that connects the JVM to the underlying operating system. Note The “glue” code consists of platform-specific libraries for accessing the operating system’s windowing, networking, and other subsystems. It also consists of code that uses the Java Native Interface (JNI) to bridge between Java and the operating system. I discuss the JNI in Appendix C. You might also want to check out Wikipedia’s “Java Native Interface” entry (http://en.wikipedia.org/wiki/Java_Native_Interface) to learn about the JNI. When a Java program launcher starts the Java platform, the JVM is launched and told to load a Java program’s starting classfile into memory, via a component known as a classloader. After the classfile has loaded, the following tasks are performed: • The classfile’s bytecode instruction sequences are verified to ensure that they don’t compromise the security of the JVM and underlying environment. Verification ensures that a sequence of instructions doesn’t find a way to exploit the JVM to corrupt the environment and possibly steal sensitive information. The component that handles this task is known as the bytecode verifier. • The classfile’s main sequence of bytecode instructions is executed. The component that handles this task is known as the interpreter because instructions are interpreted (identified and used to select appropriate sequences of native processor instructions to carry out the equivalent of what the bytecode instructions mean). When the interpreter discovers that a bytecode instruction sequence is executed repeatedly, it informs the Just-In-Time (JIT) compiler component to compile this sequence into an equivalent sequence of native instructions. The JIT helps the Java program achieve faster execution than would be possible through interpretation alone. Note that the JIT and the Java compiler that compiles source code into bytecode are two separate compilers with two different goals. 3 CHAPTER 1 GETTING STARTED WITH JAVA During execution, a classfile might refer to another classfile. In this situation, a classloader is used to load the referenced classfile, the bytecode verifier then verifies the classfile’s bytecodes, and the interpreter/JIT executes the appropriate bytecode sequence in this other classfile. The Java platform was designed with portability in mind. By providing an abstraction over the underlying operating system, bytecode instruction sequences should execute consistently across Java platforms. However, this isn’t always borne out in practice. For example, many Java platforms rely on the underlying operating system to schedule threads (discussed in Chapter 4), and the thread scheduling implementation varies from operating system to operating system. As a result, you must be careful to ensure that the program is designed to adapt to these vagaries. The Java platform was also designed with security in mind. As well as the bytecode verifier, the platform provides a security framework to help ensure that malicious programs don’t corrupt the underlying environment on which the program is running. Appendix C discusses Java’s security framework. Installing and Working with JDK 7 Three software development kits (SDKs) exist for developing different kinds of Java programs: • The Java SE (Standard Edition) Software Development Kit (known as the JDK) is used to create desktop-oriented standalone applications and web browserembedded applications known as applets. You are introduced to standalone applications later in this section. I don’t discuss applets because they aren’t as popular as they once were. • The Java ME (Mobile Edition) SDK is used to create applications known as MIDlets and Xlets. MIDlets target mobile devices, which have small graphical displays, simple numeric keypad interfaces, and limited HTTP-based network access. Xlets typically target television-oriented devices such as Blu-ray Disc players. The Java ME SDK requires that the JDK also be installed. I don’t discuss MIDlets or Xlets. • The Java EE (Enterprise Edition) SDK is used to create component-based enterprise applications. Components include servlets, which can be thought of as the server equivalent of applets, and servlet-based Java Server Pages (JSPs). The Java EE SDK requires that the JDK also be installed. I don’t discuss servlets. This section introduces you to JDK 7 (also referred to as Java 7, a term used in later chapters) by first showing you how to install this latest major Java SE release. It then shows you how to use JDK 7 tools to develop a simple standalone application—I’ll use the shorter application term from now on. Installing JDK 7 Point your browser to http://www.oracle.com/technetwork/java/javase/downloads/index-jsp138363.html and follow the instructions on the resulting web page to download the appropriate JDK 7 installation exe or gzip tarball file for your Windows, Solaris, or Linux platform. Following the download, run the Windows executable or unarchive the Solaris/Linux gzip tarball, and modify your PATH environment variable to include the resulting home directory’s bin subdirectory so that you can run JDK 7 tools from anywhere in your filesystem. For example, you might include the C:\Program Files\Java\jdk1.7.0 home directory in the PATH on a Windows platform. You should also update your JAVA_HOME environment variable to point to JDK 7’s home directory, to ensure that any Javadependent software can find this directory. 4 CHAPTER 1 GETTING STARTED WITH JAVA JDK 7’s home directory contains several files (e.g., README.html and LICENSE) and subdirectories. The most important subdirectory from this book’s perspective is bin, which contains various tools that we’ll use throughout this book. The following list identifies some of these tools: • jar: a tool for packaging classfiles and resource files into special ZIP files with “.jar” file extensions • java: a tool for running applications • javac: a tool that launches the Java compiler to compile one or more source files • javadoc: a tool that generates special HTML-based documentation from Javadoc comments The JDK’s tools are run in a command-line environment. You establish this by launching a command window (Windows) or shell (Linux/Solaris), which presents to you a sequence of prompts for entering commands (program names and their arguments). For example, a command window (on Windows platforms) prompts you to enter a command by presenting a drive letter and path combination (e.g., C:\). You respond to the prompt by typing the command, and then press the Return/Enter key to tell the operating system to execute the command. For example, javac x.java followed by a Return/Enter key press causes the operating system to launch the javac tool, and to pass the name of the source file being compiled (x.java) to this tool as its command-line argument. If you specified the asterisk (*) wildcard character, as in javac *.java, javac would compile all source files in the current directory. To learn more about working at the command line, check out Wikipedia’s “Command-line interface” entry (http://en.wikipedia.org/wiki/Command-line_interface). Another important subdirectory is jre, which stores the JDK’s private copy of the Java Runtime Environment (JRE). The JRE implements the Java platform, making it possible to run Java programs. Users interested in running (but not developing) Java programs would download the public JRE. Because the JDK contains its own copy of the JRE, developers do not need to download and install the public JRE. Note JDK 7 comes with external documentation that includes an extensive reference to Java’s many APIs (see http://en.wikipedia.org/wiki/Application_programming_interface to learn about this term). You can download the documentation archive from http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html so that you can view this documentation offline. However, because the archive is fairly large, you might prefer to view the documentation online at http://download.oracle.com/javase/7/docs/index.html. Working with JDK 7 An application consists of a class with an entry-point method named main. Although a proper discussion of classes and methods must wait until Chapter 2, it suffices for now to just think of a class as a factory for creating objects (also discussed in Chapter 2), and to think of a method as a named sequence of instructions that are executed when the method is called. Listing 1-1 introduces you to your first application. 5 CHAPTER 1 GETTING STARTED WITH JAVA Listing 1-1. Greetings from Java class HelloWorld { public static void main(String[] args) { System.out.println("Hello, world!"); } } Listing 1-1 declares a class named HelloWorld that provides a framework for this simple application. It also declares a method named main within this class. When you run this application, and you will learn how to do so shortly, it is this entry-point method that is called and its instructions that are executed. The main() method includes a header that identifies this method and a block of code located between an open brace character ({) and a close brace character (}). As well as naming this method, the header provides the following information: • public: This reserved word makes main() visible to the startup code that calls this method. If public wasn’t present, the compiler would output an error message stating that it could not find a main() method. • static: This reserved word causes this method to associate with the class instead of associating with any objects created from this class. Because the startup code that calls main() doesn’t create an object from the class in order to call this method, it requires that the method be declared static. Although the compiler will not report an error if static is missing, it will not be possible to run HelloWorld, which will not be an application if the proper main() method doesn’t exist. • void: This reserved word indicates that the method doesn’t return a value. If you change void to a type’s reserved word (e.g., int) and then insert a statement that returns a value of this type (e.g., return 0;), the compiler will not report an error. However, you won’t be able to run HelloWorld because the proper main() method would not exist. • (String[] args): This parameter list consists of a single parameter named args of type String[]. Startup code passes a sequence of command-line arguments to args, which makes these arguments available to the code that executes within main(). You’ll learn about parameters and arguments in Chapter 2. The block of code consists of a single System.out.println("Hello, world!"); method call. From left to write, System identifies a standard class of system utilities, out identifies an object variable located in System whose methods let you output values of various types optionally followed by a newline character to the standard output device, println identifies a method that prints its argument followed by a newline character to standard output, and "Hello, world!" is a string (a sequence of characters delimited by double quote " characters and treated as a unit) that is passed as the argument to println and written to standard output (the starting " and ending " double quote characters are not written; these characters delimit but are not part of the string). 6 CHAPTER 1 GETTING STARTED WITH JAVA Note All desktop Java/nonJava applications can be run at the command line. Before graphical user interfaces with their controls for inputting and outputting values (e.g., textfields), these applications obtained their input and generated their output with the help of Standard I/O, an input/output mechanism that originated with the Unix operating system, and which consists of standard input, standard output, and standard error devices. The user would input data via the standard input device (typically the keyboard, but a file could be specified instead—Unix treats everything as files). The application’s output would appear on the standard output device (typically a computer screen, but optionally a file or printer). Output messages denoting errors would be output to the standard error device (screen, file, or printer) so that these messages could be handled separately. Now that you understand how Listing 1-1 works, you’ll want to create this application. Complete the following steps to accomplish this task: 1. Copy Listing 1-1 to a file named HelloWorld.java. 2. Execute javac HelloWorld.java to compile this source file. javac will complain if you do not specify the “.java” file extension. If all goes well, you should see a HelloWorld.class file in the current directory. Now execute java HelloWorld to run this classfile’s main() method. Don’t specify the “.class” file extension or java will complain. You should observe the following output: Hello, world! Congratulations! You have run your first Java-based application. You’ll have an opportunity to run more applications throughout this book. Installing and Working with NetBeans 7 For small projects, it’s no big deal to work at the command line with JDK tools. Because you’ll probably find this scenario tedious (and even unworkable) for larger projects, you should consider obtaining an Integrated Development Environment (IDE) tool. Three popular IDEs for Java development are Eclipse (http://www.eclipse.org/), IntelliJ IDEA (http://www.jetbrains.com/idea/), which is free to try but must be purchased if you want to continue to use it, and NetBeans (http://netbeans.org/). I focus on the NetBeans 7 IDE in this section because of its JDK 7 support. (IntelliJ IDEA 10.5 also supports JDK 7.) Note For a list of NetBeans 7 IDE enhancements that are specific to JDK 7, check out the page at http://wiki.netbeans.org/NewAndNoteworthyNB70#JDK7_support. 7 CHAPTER 1 GETTING STARTED WITH JAVA This section shows you how to install the NetBeans 7 IDE. It then introduces you to this IDE while developing HelloWorld. Note NetBeans is more than an IDE. It’s also a platform framework that lets developers create applications much faster by leveraging the modular NetBeans architecture. Installing NetBeans 7 Point your browser to http://netbeans.org/downloads/ and perform the following tasks: 1. Select an appropriate IDE language (English is the default). 2. Select an appropriate platform (Windows is the default). 3. Click the Download button underneath the next-to-leftmost (Java EE) column to initiate the download process for the appropriate installer file. I chose to download the English Java EE installer for the Windows platform, which is a file named netbeans-7.x-ml-javaee-windows.exe. (Because I don’t explore Java EE in Beginning Java 7, it might seem pointless to install the Java EE version of NetBeans. However, you might as well install this software now in case you decide to explore Java EE after reading this book.) Run the installer. After configuring itself, the installer presents a Welcome dialog that gives you the option of choosing which application servers you want to install with the IDE. Ensure that both the GlassFish Server and Apache Tomcat checkboxes remain checked (you might want to play with both application servers when exploring Java EE), and click the Next button. On the resulting License Agreement dialog, read the agreement, indicate its acceptance by checking the checkbox, and click Next. Repeat this process on the subsequent JUnit License Agreement dialog. The resulting NetBeans IDE 7.0 Installation dialog presents the default location where NetBeans will be installed (C:\Program Files\NetBeans 7.0 on my platform) and the JDK 7 home directory location (C:\Program Files\Java\jdk1.7.0 on my platform). Change these locations if necessary and click Next. The resulting GlassFish 3.1 Installation dialog box presents the default location where the GlassFish application server will be installed (C:\Program Files\glassfish-3.1 on my platform). Change this location if necessary and click Next. The resulting Apache Tomcat 7.0.11 Installation dialog presents the default location where the Apache Tomcat application server will be installed (C:\Program Files\Apache Software Foundation\Apache Tomcat 7.0.11 on my platform). Change this location if necessary and click Next. The resulting Summary dialog presents your chosen options as well as the combined installation size for all software being installed. After reviewing this information, click the Install button to begin installation. Installation takes a few minutes and culminates in a Setup Complete dialog. After reviewing this dialog’s information, click the Finish button to complete installation. Assuming a successful installation, start this IDE. NetBeans first presents a splash screen while it performs various initialization tasks, and then presents a main window similar to that shown in Figure 11. 8 This book was purchased by [email protected] CHAPTER 1 GETTING STARTED WITH JAVA Figure 1-1. The NetBeans 7 IDE’s main window initially presents a Start Page tab. If you’ve worked with previous versions of the NetBeans IDE, you might want to click the Take a Tour button to learn how version 7 differs from its predecessors. You are taken to a web page that provides video tours of the IDE, such as NetBeans IDE 7.0 Overview. Working with NetBeans 7 NetBeans presents a user interface whose main window is divided into a menu bar, a toolbar, a workspace, and a status bar. The workspace presents a Start Page tab for learning about NetBeans, accessing your NetBeans projects, and more. To help you get comfortable with this IDE, I’ll show you how to create a HelloWorld project that reuses Listing 1-1’s source code. I’ll also show you how to compile and run the HelloWorld application. Complete the following steps to create the HelloWorld project: 1. Select New Project from the File menu. 2. Make sure that Java is the selected category and Java Application is the selected Project in their respective Categories and Projects lists on the resulting New Project dialog box’s Choose Project pane. Click Next. 3. On the resulting Name and Location pane, enter HelloWorld into the Project Name textfield. Notice that helloworld.HelloWorld appears in the textfield to the right of the Create Main Class checkbox (which must be checked). The 9 CHAPTER 1 GETTING STARTED WITH JAVA helloworld portion of this string refers to a package that stores the HelloWorld class portion of this string. (Packages are discussed in Chapter 3.) Click Finish. NetBeans spends a few moments creating the HelloWorld project. Once it finishes, NetBeans presents the workspace shown in Figure 1-2. Figure 1-2. The workspace is organized into multiple work areas. After creating HelloWorld, NetBeans organizes the workspace into projects, editor, navigator, and tasks work areas. The projects area helps you manage your projects and is organized into the following tabs: • The Projects tab is the main entry point to your project’s source and resource files. It presents a logical view of important project contents. • The Files tab presents a directory-based view of your projects. This view includes any files and folders not shown on the Projects tab. • The Services tab presents a logical view of resources registered with the IDE, for example, servers, databases, and web services. The editor area helps you edit a project’s source files. Each file is associated with its own tab, which is labeled with the filename. For example, Figure 1-2 reveals a HelloWorld.java tab that provides a skeletal version of this source file’s contents. 10 CHAPTER 1 GETTING STARTED WITH JAVA The navigator area presents the Navigator tab, which offers a compact view of the currently selected file, and which simplifies navigation between various parts of the file (e.g., class and method headers). Finally, the task area presents a Tasks tab that reveals a to-do list of items that need to be resolved for the project’s various files. Each item consists of a description, a filename, and the location within the file where resolution must take place. Replace the HelloWorld.java tab’s contents with Listing 1-1, keeping the package helloworld; statement at the top of the file to prevent NetBeans from complaining about an incorrect package. Continuing, select Run Main Project from the Run menu to compile and run this application. Figure 13’s Output tab shows HelloWorld’s greeting. Figure 1-3. An Output tab appears to the left of Tasks and shows HelloWorld’s greeting. Tip To pass command-line arguments to an application, first select Project Properties from the File menu. On the resulting Project Properties dialog box, select Run in the Categories tree, and enter the arguments (separated by spaces; for example, first second third) in the Arguments textfield on the resulting pane. For more information on the NetBeans 7 IDE, study the tutorials via the Start Page tab, access IDE help via the Help menu, and explore the NetBeans knowledge base at http://netbeans.org/kb/. 11 CHAPTER 1 GETTING STARTED WITH JAVA Java Language Fundamentals Most computer languages support comments, identifiers, types, variables, expressions, and statements. Java is no exception, and this section introduces you to these fundamental language features from Java’s perspective. Comments A program’s source code needs to be documented so that you (and any others who have to maintain it) can understand it, now and later. Source code should be documented while being written and whenever it is modified. If these modifications impact existing documentation, the documentation must be updated so that it accurately explains the code. Java provides the comment feature for embedding documentation in source code. When the source code is compiled, the Java compiler ignores all comments—no bytecodes are generated. Single-line, multiline, and Javadoc comments are supported. Single-Line Comments A single-line comment occupies all or part of a single line of source code. This comment begins with the // character sequence and continues with explanatory text. The compiler ignores everything from // to the end of the line in which // appears. The following example presents a single-line comment: int x = (int) (Math.random()*100); // Obtain a random x coordinate from 0 through 99. Single-line comments are useful for inserting short but meaningful explanations of source code into this code. Don’t use them to insert unhelpful information. For example, when declaring a variable, don’t insert a meaningless comment such as // this variable is an integer. Multiline Comments A multiline comment occupies one or more lines of source code. This comment begins with the /* character sequence, continues with explanatory text, and ends with the */ character sequence. Everything from /* through */ is ignored by the compiler. The following example demonstrates a multiline comment: static boolean isLeapYear(int year) { /* A year is a leap year if it is divisible by 400, or divisible by 4 but not also divisible by 100. */ if (year%400 == 0) return true; else if (year%100 == 0) return false; else if (year%4 == 0) return true; else 12 CHAPTER 1 GETTING STARTED WITH JAVA return false; } This example introduces a method for determining whether or not a year is a leap year. The important part of this code to understand is the multiline comment, which clarifies the expression that determines whether year’s value does or doesn’t represent a leap year. Caution You cannot place one multiline comment inside another. For example, /*/* Nesting multiline comments is illegal! */*/ is not a valid multiline comment. Javadoc Comments A Javadoc comment (also known as a documentation comment) occupies one or more lines of source code. This comment begins with the /** character sequence, continues with explanatory text, and ends with the */ character sequence. Everything from /** through */ is ignored by the compiler. The following example demonstrates a Javadoc comment: /** * Application entry point * * @param args array of command-line arguments passed to this method */ public static void main(String[] args) { // TODO code application logic here } This example begins with a Javadoc comment that describes the main() method. Sandwiched between /** and */ is a description of the method, which could (but doesn’t) include HTML tags (such as <p> and <code>/</code>), and the @param Javadoc tag (an @-prefixed instruction). The following list identifies several commonly used tags: • @author identifies the source code’s author. • @deprecated identifies a source code entity (e.g., a method) that should no longer be used. • @param identifies one of a method’s parameters. • @see provides a see-also reference. • @since identifies the software release where the entity first originated. • @return identifies the kind of value that the method returns. Listing 1-2 presents our HelloWorld application with documentation comments that describe the HelloWorld class and its main() method. 13 CHAPTER 1 GETTING STARTED WITH JAVA Listing 1-2. Greetings from Java with documentation comments /** A simple class for introducing a Java application. @author Jeff Friesen */ class HelloWorld { /** Application entry point @param args array of command-line arguments passed to this method */ public static void main(String[] args) { System.out.println("Hello, world!"); } } We can extract these documentation comments into a set of HTML files by using the JDK’s javadoc tool, as follows: javadoc -private HelloWorld.java javadoc defaults to generating HTML-based documentation for public classes and public/protected members of these classes—you’ll learn about these concepts in Chapter 2. Because HelloWorld is not public, specifying javadoc HelloWorld.java causes javadoc to complain that no public or protected classes were found to document. The remedy is to specify javadoc’s -private commandline option. javadoc responds by outputting the following messages: Loading source file HelloWorld.java... Constructing Javadoc information... Standard Doclet version 1.7.0 Building tree for all the packages and classes... Generating \HelloWorld.html... Generating \package-frame.html... Generating \package-summary.html... Generating \package-tree.html... Generating \constant-values.html... Building index for all the packages and classes... Generating \overview-tree.html... Generating \index-all.html... Generating \deprecated-list.html... Building index for all classes... Generating \allclasses-frame.html... Generating \allclasses-noframe.html... Generating \index.html... Generating \help-doc.html... 14 CHAPTER 1 GETTING STARTED WITH JAVA It also generates several files, including the index.html entry-point file. Point your browser to this file and you should see a page similar to that shown in Figure 1-4. Figure 1-4. The entry-point page into HelloWorld’s javadoc provides easy access to the documentation. Note JDK 7’s external documentation has a similar appearance and organization to Figure 1-4 because this documentation was also generated by javadoc. Identifiers Source code entities such as classes and methods need to be named so that they can be referenced from elsewhere in the code. Java provides the identifiers feature for this purpose. An identifier consists of letters (A-Z, a-z, or equivalent uppercase/lowercase letters in other human alphabets), digits (0-9 or equivalent digits in other human alphabets), connecting punctuation characters (e.g., the underscore), and currency symbols (e.g., the dollar sign $). This name must begin with a letter, a currency symbol, or a connecting punctuation character; and its length cannot exceed the line in which it appears. Examples of valid identifiers include i, counter, loop10, border$color and _char. Examples of invalid identifiers include 50y (starts with a digit) and first#name (# is not a valid identifier symbol). 15 CHAPTER 1 GETTING STARTED WITH JAVA Note Java is a case-sensitive language, which means that identifiers differing only in case are considered separate identifiers. For example, salary and Salary are separate identifiers. Almost any valid identifier can be chosen to name a class, method, or other source code entity. However, some identifiers are reserved for special purposes; they are known as reserved words. Java reserves the following identifiers: abstract, assert, boolean, break, byte, case, catch, char, class, const, continue, default, do, double, enum, else, extends, false, final, finally, float, for, goto, if, implements, import, instanceof, int, interface, long, native, new, null, package, private, protected, public, return, short, static, strictfp, super, switch, synchronized, this, throw, throws, transient, true, try, void, volatile, and while. The compiler outputs an error message if you attempt to use any of these reserved words outside of their usage contexts. Note Most of Java’s reserved words are also known as keywords. The three exceptions are false, null, and true, which are examples of literals (values specified verbatim). Types Programs process different types of values such as integers, floating-point values, characters, and strings. A type identifies a set of values (and their representation in memory) and a set of operations that transform these values into other values of that set. For example, the integer type identifies numeric values with no fractional parts and integer-oriented math operations, such as adding two integers to yield another integer. Note Java is a strongly typed language, which means that every expression, variable, and so on has a type known to the compiler. This capability helps the compiler detect type-related errors at compile time rather than having these errors manifest themselves at runtime. Expressions and variables are discussed later in this chapter. Java classifies types as primitive types, user-defined types, and array types. Primitive Types A primitive type is a type that is defined by the language and whose values are not objects. Java supports the Boolean, character, byte integer, short integer, integer, long integer, floating-point, and double precision floating-point primitive types. They are described in Table 1-1. 16 CHAPTER 1 GETTING STARTED WITH JAVA Table 1-1. Primitive Types Primitive Type Reserved Word Size Min Value Max Value Boolean boolean -- -- -- Character char 16-bit Unicode 0 Unicode 216 - 1 Byte integer byte 8-bit -128 +127 Short integer short 16-bit -215 +215 - 1 Integer int 32-bit -231 +231 - 1 Long integer long 64-bit -2 63 +263 - 1 Floating-point float 32-bit IEEE 754 IEEE 754 Double precision floating-point double 64-bit IEEE 754 IEEE 754 Table 1-1 describes each primitive type in terms of its reserved word, size, minimum value, and maximum value. A “--” entry indicates that the column in which it appears is not applicable to the primitive type described in that entry’s row. The size column identifies the size of each primitive type in terms of the number of bits (binary digits—each digit is either 0 or 1) that a value of that type occupies in memory. Except for Boolean (whose size is implementation dependent—one Java implementation might store a Boolean value in a single bit, whereas another implementation might require an eight-bit byte for performance efficiency), each primitive type’s implementation has a specific size. The minimum value and maximum value columns identify the smallest and largest values that can be represented by each type. Except for Boolean (whose only values are true and false), each primitive type has a minimum value and a maximum value. The minimum and maximum values of the character type refer to Unicode, which is a standard for the consistent encoding, representation, and handling of text expressed in most of the world's writing systems. Unicode was developed in conjunction with the Universal Character Set, a standard for encoding the various symbols making up the world’s written languages. Unicode 0 is shorthand for “the first Unicode code point”—a code point is an integer that represents a symbol (e.g., A) or a control character (e.g., newline or tab), or that combines with other code points to form a symbol. Check out Wikipedia’s “Unicode” entry (http://en.wikipedia.org/wiki/Unicode) to learn more about this standard, and Wikipedia’s “Universal Character Set” entry (http://en.wikipedia.org/wiki/Universal_Character_Set) to learn more about this standard. Note The character type’s limits imply that this type is unsigned (all character values are positive). In contrast, each numeric type is signed (it supports positive and negative values). 17 CHAPTER 1 GETTING STARTED WITH JAVA The minimum and maximum values of the byte integer, short integer, integer, and long integer types reveal that there is one more negative value than positive value (0 is typically not regarded as a positive value). The reason for this imbalance has to do with how integers are represented. Java represents an integer value as a combination of a sign bit (the leftmost bit—0 for a positive value and 1 for a negative value) and magnitude bits (all remaining bits to the right of the sign bit). If the sign bit is 0, the magnitude is stored directly. However, if the sign bit is 1, the magnitude is stored using twos-complement representation in which all 1s are flipped to 0s, all 0s are flipped to 1s, and 1 is added to the result. Twos-complement is used so that negative integers can naturally coexist with positive integers. For example, adding the representation of -1 to +1 yields 0. Figure 1-5 illustrates byte integer 2’s direct representation and byte integer -2’s twos-complement representation. Figure 1-5. The binary representation of two byte integer values begins with a sign bit. The minimum and maximum values of the floating-point and double precision floating-point types refer to IEEE 754, which is a standard for representing floating-point values in memory. Check out Wikipedia’s “IEEE 754-2008” entry (http://en.wikipedia.org/wiki/IEEE_754) to learn more about this standard. Note Developers who argue that Java should only support objects are not happy about the inclusion of primitive types in the language. However, Java was designed to include primitive types to overcome the speed and memory limitations of early 1990s-era devices, to which Java was originally targeted. User-Defined Types A user-defined type is a type that is defined by the developer using a class, an interface, an enum, or an annotation type; and whose values are objects. For example, Java’s String class defines the string userdefined type; its values describe strings of characters, and its methods perform various string operations such as concatenating two strings together. Chapter 2 discusses classes, interfaces, and methods. Chapter 3 discusses enums and annotation types. User-defined types are also known as reference types because a variable of that type stores a reference (a memory address or some other identifier) to a region of memory that stores an object of that type. In contrast, variables of primitive types store the values directly; they don’t store references to these values. Array Types An array type is a special reference type that signifies an array, a region of memory that stores values in equal-size and contiguous slots, which are commonly referred to as elements. 18 CHAPTER 1 GETTING STARTED WITH JAVA This type consists of the element type (a primitive type or a user-defined type) and one or more pairs of square brackets that indicate the number of dimensions (extents). A single pair of brackets signifies a one-dimensional array (a vector), two pairs of brackets signify a two-dimensional array (a table), three pairs of brackets signify a one-dimensional array of two-dimensional arrays (a vector of tables), and so on. For example, int[] signifies a one-dimensional array (with int as the element type), and double[][] signifies a two-dimensional array (with double as the element type). Variables This book was purchased by [email protected] Programs manipulate values that are stored in memory, which is symbolically represented in source code through the use of the variables feature. A variable is a named memory location that stores some type of value. Variables that store references are often referred to as reference variables. Variables must be declared before they are used. A declaration minimally consists of a type name, optionally followed by a sequence of square bracket pairs, followed by a name, optionally followed by a sequence of square bracket pairs, and terminated with a semicolon character (;). Consider the following examples: int counter; double temperature; String firstName; int[] ages; char gradeLetters[]; float[][] matrix; The first example declares an integer variable named counter, the second example declares a double precision floating-point variable named temperature, the third example declares a string variable named firstName, the fourth example declares a one-dimensional integer array variable named ages, the fifth example declares a one-dimensional character array variable named gradeLetters, and the sixth example declares a two-dimensional floating-point array variable named matrix. No string is yet associated with firstName, and no arrays are yet associated with ages, gradeLetters, and matrix. Caution Square brackets can appear after the type name or after the variable name, but not in both places. For example, the compiler reports an error when it encounters int[] x[];. It is common practice to place the square brackets after the type name (as in int[] ages;) instead of after the variable name (as in char gradeLetters[];). You can declare multiple variables on one line by separating each variable from its predecessor with a comma, as demonstrated by the following example: int x, y[], z; This example declares three variables named x, y, and z. Each variable shares the same type, which happens to be integer. Unlike x and z, which store single integer values, y[] signifies a one-dimensional array whose element type is integer – each element stores an integer value. No array is yet associated with y. The square brackets must appear after the variable name when the array is declared on the same line as the other variables. If you place the square brackets before the variable name, as in int x, []y, 19 CHAPTER 1 GETTING STARTED WITH JAVA z;, the compiler reports an error. If you place the square brackets after the type name, as in int[] x, y, z;, all three variables signify one-dimensional arrays of integers. Expressions The previously declared variables were not explicitly initialized to any values. As a result, they are either initialized to default values (e.g., 0 for int and 0.0 for double) or remain uninitialized, depending upon the contexts in which they appear (declared within classes or declared within methods). Chapter 2 discusses variable contexts in terms of fields, local variables, and parameters. Java provides the expressions feature for initializing variables and for other purposes. An expression is a combination of literals, variable names, method calls, and operators. At runtime, it evaluates to a value whose type is referred to as the expression’s type. If the expression is being assigned to a variable, the expression’s type must agree with the variable’s type; otherwise, the compiler reports an error. Java classifies expressions as simple expressions and compound expressions. Simple Expressions A simple expression is a literal (a value expressed verbatim), a variable name (containing a value), or a method call (returning a value). Java supports several kinds of literals: string, Boolean true and false, character, integer, floating-point, and null. Note A method call that doesn’t return a value—the called method is known as a void method—is a special kind of simple expression; for example, System.out.println("Hello, World!");. This standalone expression cannot be assigned to a variable. Attempting to do so (as in int i = System.out.println("X");) causes the compiler to report an error. A string literal consists of a sequence of Unicode characters surrounded by a pair of double quotes; for example, "The quick brown fox jumps over the lazy dog." It might also contain escape sequences, which are special syntax for representing certain printable and nonprintable characters that otherwise cannot appear in the literal. For example, "The quick brown \"fox\" jumps over the lazy dog." uses the \" escape sequence to surround fox with double quotes. Table 1-2 describes all supported escape sequences. 20 CHAPTER 1 GETTING STARTED WITH JAVA Table 1-2. Escape Sequences Escape Syntax Description \\ Backslash \" Double quote \' Single quote \b Backspace \f Form feed \n Newline (also referred to as line feed) \r Carriage return \t Horizontal tab Finally, a string literal might contain Unicode escape sequences, which are special syntax for representing Unicode characters. A Unicode escape sequence begins with \u and continues with four hexadecimal digits (0-9, A-F, a-f) with no intervening space. For example, \u0041 represents capital letter A, and \u20ac represents the European Union’s euro currency symbol. A Boolean literal consists of reserved word true or reserved word false. A character literal consists of a single Unicode character surrounded by a pair of single quotes ('A' is an example). You can also represent, as a character literal, an escape sequence ('\'', for example) or a Unicode escape sequence (e.g., '\u0041'). An integer literal consists of a sequence of digits. If the literal is to represent a long integer value, it must be suffixed with an uppercase L or lowercase l (L is easier to read). If there is no suffix, the literal represents a 32-bit integer (an int). Integer literals can be specified in the decimal, hexadecimal, octal, and binary formats: • The decimal format is the default format; for example, 127. • The hexadecimal format requires that the literal begin with 0x or 0X and continue with hexadecimal digits (0-9, A-F, a-f); for example, 0x7F. • The octal format requires that the literal be prefixed with 0 and continue with octal digits (0-7); for example, 0177. • The binary format requires that the literal be prefixed with 0b or 0B and continue with 0s and 1s; for example, 0b01111111. To improve readability, you can insert underscores between digits; for example, 204_555_1212. Although you can insert multiple successive underscores between digits (as in 0b1111__0000), you cannot specify a leading underscore (as in _123) because the compiler would treat the literal as an 21 CHAPTER 1 GETTING STARTED WITH JAVA identifier. Also, you cannot specify a trailing underscore (as in 123_). A floating-point literal consists of an integer part, a decimal point (represented by the period character [.]), a fractional part, an exponent (starting with letter E or e), and a type suffix (letter D, d, F, or f). Most parts are optional, but enough information must be present to differentiate the floating-point literal from an integer literal. Examples include 0.1 (double precision floating-point), 89F (floating-point), 600D (double precision floatingpoint), and 13.08E+23 (double precision floating-point). As with integer literals, you can make floatingpoint literals easier to read by placing underscores between digits (3.141_592_654, for example). Finally, the null literal is assigned to a reference variable to indicate that the variable does not refer to an object. The following examples use literals to initialize the previously presented variables: int counter = 10; double temperature = 98.6; // Assume Fahrenheit scale. String firstName = "Mark"; int[] ages = { 52, 28, 93, 16 }; char gradeLetters[] = { 'A', 'B', 'C', 'D', 'F' }; float[][] matrix = { { 1.0F, 2.0F, 3.0F }, { 4.0F, 5.0F, 6.0F }}; int x = 1, y[] = { 1, 2, 3 }, z = 3; The last four examples use array initializers to initialize the ages, gradeletters, matrix, and y arrays. An array initializer consists of a brace-and-comma-delimited list of expressions, which (as the matrix example shows) may themselves be array initializers. The matrix example results in a table that looks like the following: 1.0F 2.0F 3.0F 4.0F 5.0F 6.0F ORGANIZING VARIABLES IN MEMORY Perhaps you’re curious about how variables are organized in memory. Figure 1-6 presents one possible high-level organization for the counter, ages, and matrix variables, along with the arrays assigned to ages and matrix. Figure 1-6. The counter variable stores a four-byte integer value, whereas ages and matrix store four-byte references to their respective arrays. Figure 1-6 reveals that each of counter, ages, and matrix is stored at a memory address (starting at a fictitious 20001000 value in this example) and divisible by four (each variable stores a four-byte value), that counter’s four-byte value is stored at this address, and that each of the ages and matrix four-byte 22 CHAPTER 1 GETTING STARTED WITH JAVA memory locations stores the 32-bit address of its respective array (64-bit addresses would most likely be used on 64-bit JVMs). Also, a one-dimensional array is stored as a list of values, whereas a twodimensional array is stored as a one-dimensional row array of addresses, where each address identifies a one-dimensional column array of values for that row. Although Figure 1-6 implies that array addresses are stored in ages and matrix, which equates references with addresses, a Java implementation might equate references with handles (integer values that identify slots in a list). This alternative is presented in Figure 1-7 for ages and its referenced array. Figure 1-7. A handle is stored in ages, and the list entry identified by this handle stores the address of the associated array. Handles make it easy to move around regions of memory during garbage collection (discussed in Chapter 2). If multiple variables referenced the same array via the same address, each variable’s address value would have to be updated when the array was moved. However, if multiple variables referenced the array via the same handle, only the handle’s list entry would need to be updated. A downside to using handles is that accessing memory via these handles can be slower than directly accessing this memory via an address. Regardless of how references are implemented, this implementation detail is hidden from the Java developer in order to promote portability. The following example shows a simple expression where one variable is assigned the value of another variable: int counter1 = 1; int counter2 = counter1; Finally, the following example shows a simple expression that assigns the result of a method call to a variable named isLeap: boolean isLeap = isLeapYear(2011); The previous examples have assumed that only those expressions whose types are the same as the types of the variables that they are initializing can be assigned to those variables. However, under certain circumstances, it’s possible to assign an expression having a different type. For example, Java permits you to assign certain integer literals to short integer variables, as in short s = 20;, and assign a short integer expression to an integer variable, as in int i = s;. Java permits the former assignment because 20 can be represented as a short integer (no information is lost). In contrast, Java would complain about short s = 40000; because integer literal 40000 cannot be represented as a short integer (32767 is the maximum positive integer that can be stored in a short integer variable). Java permits the latter assignment because no information is lost when Java converts from a type with a smaller set of values to a type with a wider set of values. Java supports the following primitive type conversions via widening conversion rules: 23 CHAPTER 1 GETTING STARTED WITH JAVA • Byte integer to short integer, integer, long integer, floating-point, or double precision floating-point • Short integer to integer, long integer, floating-point, or double precision floatingpoint • Character to integer, long integer, floating-point, or double precision floatingpoint • Integer to long integer, floating-point, or double precision floating-point • Long integer to floating-point or double precision floating-point • Floating-point to double precision floating-point Note When converting from a smaller integer to a larger integer, Java copies the smaller integer’s sign bit into the extra bits of the larger integer. Chapter 2 discusses the widening conversion rules for performing type conversions in the context of user-defined and array types. Compound Expressions A compound expression is a sequence of simple expressions and operators, where an operator (a sequence of instructions symbolically represented in source code) transforms its operand expression value(s) into another value. For example, -6 is a compound expression consisting of operator - and integer literal 6 as its operand. This expression transforms 6 into its negative equivalent. Similarly, x+5 is a compound expression consisting of variable name x, integer literal 5, and operator + sandwiched between these operands. Variable x’s value is fetched and added to 5 when this expression is evaluated. The sum becomes the value of the expression. Note If x’s type is byte integer or short integer, this variable’s value is widened to an integer. However, if x’s type is long integer, floating-point, or double precision floating-point, 5 is widened to the appropriate type. The addition operation is performed after the widening conversion takes place. Java supplies a wide variety of operators that are classified by the number of operands they take. A unary operator takes only one operand (unary minus [-] is an example), a binary operator takes two operands (addition [+] is an example), and Java’s single ternary operator (conditional [?:]) takes three operands. Operators are also classified as prefix, postfix, and infix. A prefix operator is a unary operator that precedes its operand (as in -6), a postfix operator is a unary operator that trails its operand (as in x++), and an infix operator is a binary or ternary operator that is sandwiched between the binary operator’s 24 CHAPTER 1 GETTING STARTED WITH JAVA two or the ternary operator’s three operands (as in x+5).Table 1-3 presents all supported operators in terms of their symbols, descriptions, and precedence levels—the concept of precedence is discussed at the end of this section. Various operator descriptions refer to “integer type,” which is shorthand for specifying any of byte integer, short integer, integer, or long integer unless “integer type” is qualified as a 32-bit integer. Also, “numeric type” refers to any of these integer types along with floating-point and double precision floating-point. Table 1-3. Operators Operator Symbol Description Precedence Addition + Given operand1 + operand2, where each operand must be o f character or numeric type, add operand2 to operand1 and return the sum. 10 Array index [] Given variable[index], where index must be of integer type, read value from or store value into variable’s storage element at location index. 13 Assignment = Given variable = operand, which must be assignment-compatible (their types must agree), store operand in variable. 0 Bitwise AND & Given operand1 & operand2, where each operand must be of character or in teger type, bitwise AND their correspo nding bits and return the result. A result bit is set to 1 if each o perand’s corresponding bit is 1. Otherwise, the result bit is set to 0. 6 Bitwise complement ~ Given ~operand, where operand must be of character or integer type, flip operand’s bits (1s to 0s and 0s to 1s) and return the result. 12 Bitwise exclusive OR ^ Given operand1 ^ operand2, where each operand must be of character or in teger type, bitwise exclusive OR their corresponding bits and return the result. A result bit is set to 1 if one operand’s corresponding bit is 1 and t he other oper and’s corresponding bit is 0. Otherwise, the result bit is set to 0. 5 Bitwise inclusive OR | Given operand1 | operand2, which must be of character or integer type, bitwis e inclusive OR their corresponding bits and return the res ult. A result bit is set to 1 if eith er (or both) of the operands’ corresponding bits is 1. Otherwise, the result bit is set to 0. 4 25 CHAPTER 1 GETTING STARTED WITH JAVA Cast 26 (type) Given (type) operand, convert operand to an equivalent value that can be represented by type. For example, you could use this operator to convert a floating-point value to a 32-bit integer value. 12 Compound assignment +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>= Given variable operator operand, where operator is one of the listed compound operator symbols, and where operand is assignmentcompatible with variable, perform the indicated operation using variable’s value as operator’s left operand value, and store the resulting value in variable. 0 Conditional ?: Given operand1 ? operand2 : operand3, where operand1 must be of Boolean type, re turn operand2 if operand1 is true or operand3 if operand1 is false. The types of operand2 and operand3 must agree. 1 Conditional AND && Given operand1 && operand2, where each operand must be of Boolea n type, r eturn true if both operands are true. Ot herwise, return false. If operand1 is false, operand2 is not examined. This is known as short-circuiting. 3 Conditional OR || Given operand1 || operand2, where each operand must be of Boolea n type, r eturn true if at least one operand is true. Otherwise, return false. If operand1 is true, operand2 is not examined. This is known as short-circuiting. 2 Division / Given operand1 / operand2, where each operand must be o f character or numeric type, divide operand1 by operand2 and return the quotient. 11 Equality == Given operand1 == operand2, where both operands must be co mparable (you cannot compare an integer with a string li teral, for example), compare both operands for equality. Return true if thes e operands are equa l. Otherwise, return false. 7 Inequality != Given operand1 != operand2, where both operands must be co mparable (you cannot compare an integer with a string li teral, for example), compare both operands for inequality. Return true if these operands are not equal. 7 CHAPTER 1 GETTING STARTED WITH JAVA Otherwise, return false. Left shift << Given operand1 << operand2, where each operand must be o f character or integer type, shift operand1’s binary representation left by the number of bits that operand2 specifies. For ea ch shift, a 0 is shifted into the rightmost bit and the leftmost bit is discarded. Only the f ive low-order bits of operand2 are used wh en shifting a 32-bit integer (to prevent shifting more than the number of bits in a 32- bit integer). Only the si x low-order bits of operand2 are used when shifting a 64-bit integer (to prevent shifting more than the number of bits in a 64-bit integer). The shift preserves negative values. Furthermore, it is equivalent to (but faster than) multiplying by a multiple of 2. 9 Logical AND & Given operand1 & operand2, where each operand must be of Boolea n type, return true if both operands are true. Otherwise, return false. In contrast to conditional AND, logical AND does not perform short-circuiting. 6 Logical complement ! Given !operand, where operand must be of Boolean type, flip operand’s value (true to false or false to true) and return the result. 12 Logical exclusive OR ^ Given operand1 ^ operand2, where each operand must be of Boolea n type, return true if one operand is true and t he other operand is false. Otherwise, return false. 5 Logical inclusive OR | Given operand1 | operand2, where each operand must be of Boolea n type, return true if a t least one operand is true. Otherwise, return false. In contrast to conditional OR, logical inclusive OR does not perform short-circuiting. 4 Member access . Given identifier1.identifier2, access identifier2 member of identifer1. the 13 Method call () Given identifier(argument list), call the method identified by identifier and matching parameter list. 13 Multiplication * Given operand1 * operand2, where each operand must be o f character or numeric type, multiply 11 27 CHAPTER 1 GETTING STARTED WITH JAVA operand1 by operand2 and return the product. 28 Object creation new Given new identifier(argument list), allocate memory for object and call constructor (discussed in C hapter 2) specified as identifier(argument list). Given new identifier[integer size], allocate a onedimensional array of values. 12 Postdecrement -- Given variable--, where variable must be of character or numeric type, subtract 1 from variable’s value (storing the res ult in variable) and return the original value. 13 Postincrement ++ Given variable++, where variable must be of character or numeric type, add 1 to variable’s value (storing the result in variable) and return the original value. 13 Predecrement -- Given --variable, where variable must be of character or numeric type, subtract 1 fro m its value, store the result in variable, and return this value. 12 Preincrement ++ Given ++variable, where variable must be of character or num eric type, a dd 1 to its value, store the result in variable, and return this value. 12 Relational greater than > Given operand1 > operand2, where each operand must be o f character or numeric type, return true if operand1 is greater than operand2. Otherwise, return false. 8 Relational greater than or equal to >= Given operand1 >= operand2, where each operand must be o f character or numeric type, return true if operand1 is greater than or equal to operand2. Otherwise, return false. 8 Relational less than < Given operand1 < operand2, where each operand must be o f character or numeric type, return true if operand1 is less t han operand2. Otherwise, return false. 8 Relational less than or equal to <= Given operand1 <= operand2, where each operand must be o f character or numeric type, return true if operand1 is less than or equal to operand2. Otherwise, return false. 8 CHAPTER 1 GETTING STARTED WITH JAVA instanceof Given operand1 instanceof operand2, where operand1 is an object and operand2 is a class (o r other user-defined type), return true if operand1 is an instance of operand2. Otherwise, return false. 8 Remainder % Given operand1 % operand2, where each operand must be o f character or numeric type, divide operand1 by operand2 and return the remainder. 11 Signed right shift >> Given operand1 >> operand2, where each operand must be o f character or integer type, shift operand1’s binary repre sentation right by the number of bits that operand2 specifies. For each shift, a copy of the sign bit (the leftmost bit) is shifted to t he right a nd the rightmost bit is discarded. Only the fi ve low-order b its of operand2 are used when shifting a 32-bit integer (to prevent s hifting more than the num ber of bits in a 32-bit integer). Only the s ix low-order bits of operand2 are used wh en shifting a 64-bit integer (to prevent shifting more than the number of bi ts in a 64-bit integer). The shift preserves negative values. Furthermore, it is equivalent to (but fast er than) dividing by a multiple of 2. 9 String concatenation + Given operand1 + operand2, where at least one operand is of String type, append operand2’s string representation to operand1’s string representation and return the concatenated result. 10 Subtraction - Given operand1 - operand2, where each operand must be o f character or numeric type, subtract operand2 from operand1 and return the difference. 10 Unary minus - Given -operand, where operand must be of character or numeric type, return operand’s arithmetic negative. 12 Unary plus + Like its predecessor, but return operand. Rarely used. 12 >>> Given operand1 >>> operand2, where each operand must be o f character or integer type, shift operand1’s binary repre sentation right by the number of bits that operand2 specifies. For 9 This book was purchased by [email protected] Relational type checking Unsigned right shift 29 CHAPTER 1 GETTING STARTED WITH JAVA each shift, a zero is shifted into the leftm ost bit and the rightmost bit is discarded. Only the five low-order bits of operand2 are used when shifting a 32-bit integer (to prevent shifting more than the number of bits in a 32-bit integer). Only the six low-order bits of operand2 are used when shifting a 64-bit integer (to prevent shifting more than the number of bits in a 64-bit integer). The shift does not pres erve negative values. Furthermore, it is equivalent to (but faster than) dividing by a multiple of 2. Table 1-3’s operators can be classified as additive, array index, assignment, bitwise, cast, conditional, equality, logical, member access, method call, multiplicative, object creation, relational, shift, and unary minus/plus. Additive Operators The additive operators consist of addition (+), subtraction (-), postdecrement (--), postincrement (++), predecrement (--), preincrement (++), and string concatenation (+). Addition returns the sum of its operands (e.g., 6+4 returns 10), subtraction returns the difference between its operands (e.g., 6-4 returns 2 and 4-6 returns 2), postdecrement subtracts one from its variable operand and returns the variable’s prior value (e.g., x--), postincrement adds one to its variable operand and returns the variable’s prior value (e.g., x++), predecrement subtracts one from its variable operand and returns the variable’s new value (e.g., --x), preincrement adds one to its variable operand and returns the variable’s new value (e.g., ++x), and string concatenation merges its string operands and returns the merged string (e.g., "A"+"B" returns "AB"). The addition, subtraction, postdecrement, postincrement, predecrement, and preincrement operators can yield values that overflow or underflow the limits of the resulting value’s type. For example, adding two large positive 32-bit integer values can produce a value that cannot be represented as a 32-bit integer value. The result is said to overflow. Java does not detect overflows and underflows. Java provides a special widening conversion rule for use with string operands and the string concatenation operator. If either operand is not a string, the operand is first converted to a string prior to string concatenation. For example, when presented with "A"+5, the compiler generates code that first converts 5 to "5" and then performs the string concatenation operation, resulting in "A5". Array Index Operator The array index operator ([]) accesses an array element by presenting the location of that element as an integer index. This operator is specified after an array variable’s name; for example, ages[0]. Indexes are relative to 0, which implies that ages[0] accesses the first element, whereas ages[6] accesses the seventh element. The index must be greater than or equal to 0 and less than the length of the array; otherwise, the JVM throws ArrayIndexOutOfBoundsException (consult Chapter 3 to learn about exceptions). An array’s length is returned by appending “.length” to the array variable. For example, ages.length returns the length of (the number of elements in) the array that ages references. Similarly, matrix.length returns the number of row elements in the matrix two-dimensional array, whereas matrix[0].length returns the number of column elements assigned to the first row element of this array. 30 CHAPTER 1 GETTING STARTED WITH JAVA Assignment Operators The assignment operator (=) assigns an expression’s result to a variable (as in int x = 4;). The types of the variable and expression must agree; otherwise, the compiler reports an error. Java also supports several compound assignment operators that perform a specific operation and assign its result to a variable. For example, the += operator evaluates the numeric expression on its right and adds the result to the contents of the variable on its left. The other compound assignment operators behave in a similar way. Bitwise Operators The bitwise operators consist of bitwise AND (&), bitwise complement (~), bitwise exclusive OR (^), and bitwise inclusive OR (|). These operators are designed to work on the binary representations of their character or integral operands. Because this concept can be hard to understand if you haven’t previously worked with these operators in another language, the following example demonstrates these operators: ~0B00000000000000000000000010110101 results in 11111111111111111111111101001010 0B00011010&0B10110111 results in 00000000000000000000000000010010 0B00011010^0B10110111 results in 00000000000000000000000010101101 0B00011010|0B10110111 results in 00000000000000000000000010111111 The &, ^, and | operators in the last three lines first convert their byte integer operands to 32-bit integer values (through sign bit extension, copying the sign bit’s value into the extra bits) before performing their operations. Cast Operator The cast operator—(type)—attempts to convert the type of its operand to type. This operator exists because the compiler will not allow you to convert a value from one type to another in which information will be lost without specifying your intention do so (via the cast operator). For example, when presented with short s = 1.65+3;, the compiler reports an error because attempting to convert a double precision floating-point value to a short integer results in the loss of the fraction .65—s would contain 4 instead of 4.65. Recognizing that information loss might not always be a problem, Java permits you to explicitly state your intention by casting to the target type. For example, short s = (short) 1.65+3; tells the compiler that you want 1.65+3 to be converted to a short integer, and that you realize that the fraction will disappear. The following example provides another demonstration of the need for a cast operator: char c = 'A'; byte b = c; The compiler reports an error about loss of precision when it encounters byte b = c;. The reason is that c can represent any unsigned integer value from 0 through 65535, whereas b can only represent a signed integer value from -128 through +127. Even though 'A' equates to +65, which can fit within b’s range, c could just have easily been initialized to '\u0323', which would not fit. The solution to this problem is to introduce a (byte) cast, as follows, which causes the compiler to generate code to cast c’s character type to byte integer: byte b = (byte) c; Java supports the following primitive type conversions via cast operators: 31 CHAPTER 1 GETTING STARTED WITH JAVA • Byte integer to character • Short integer to byte integer or character • Character to byte integer or short integer • Integer to byte integer, short integer, or character • Long integer to byte integer, short integer, character, or integer • Floating-point to byte integer, short integer, character, integer, or long integer • Double precision floating-point to byte integer, short integer, character, integer, long integer, or floating-point A cast operator is not always required when converting from more to fewer bits, and where no data loss occurs. For example, when it encounters byte b = 100;, the compiler generates code that assigns integer 100 to byte integer variable b because 100 can easily fit into the 8-bit storage location assigned to this variable. Conditional Operators The conditional operators consist of conditional AND (&&), conditional OR (||), and conditional (?:). The first two operators always evaluate their left operand (a Boolean expression that evaluates to true or false) and conditionally evaluate their right operand (another Boolean expression). The third operator evaluates one of two operands based upon a third Boolean operand. Conditional AND always evaluates its left operand and evaluates its right operand only when its left operand evaluates to true. For example, age > 64 && stillWorking first evaluates age > 64. If this subexpression is true, stillWorking is evaluated, and its true or false value (stillWorking is a Boolean variable) serves as the value of the overall expression. If age > 64 is false, stillWorking is not evaluated. Conditional OR always evaluates its left operand and evaluates its right operand only when its left operand evaluates to false. For example, value < 20 || value > 40 first evaluates value < 20. If this subexpression is false, value > 40 is evaluated, and its true or false value serves as the overall expression’s value. If value < 20 is true, value > 40 is not evaluated. Conditional AND and conditional OR boost performance by preventing the unnecessary evaluation of subexpressions, which is known as short-circuiting. For example, if its left operand is false, there is no way that conditional AND’s right operand can change the fact that the overall expression will evaluate to false. If you aren’t careful, short-circuiting can prevent side effects (the results of subexpressions that persist after the subexpressions have been evaluated) from executing. For example, age > 64 && ++numEmployees > 5 increments numEmployees for only those employees whose ages are greater than 64. Incrementing numEmployees is an example of a side effect because the value in numEmployees persists after the subexpression ++numEmployees > 5 has evaluated. The conditional operator is useful for making a decision by evaluating and returning one of two operands based upon the value of a third operand. The following example converts a Boolean value to its integer equivalent (1 for true and 0 for false): boolean b = true; int i = b ? 1 : 0; // 1 assigns to i 32 CHAPTER 1 GETTING STARTED WITH JAVA Equality Operators The equality operators consist of equality (==) and inequality (!=). These operators compare their operands to determine whether they are equal or unequal. The former operator returns true when equal; the latter operator returns true when unequal. For example, each of 2 == 2 and 2 != 3 evaluates to true, whereas each of 2 == 4 and 4 != 4 evaluates to false. When it comes to object operands (discussed in Chapter 2), these operators do not compare their contents. For example, "abc" == "xyz" does not compare a with x. Instead, because string literals are really String objects stored in memory (Chapter 4 discusses this concept further), == compares the references to these objects. Logical Operators The logical operators consist of logical AND (&), logical complement (!), logical exclusive OR (^), and logical inclusive OR (|). Although these operators are similar to their bitwise counterparts, whose operands must be integer/character, the operands passed to the logical operators must be Boolean. For example, !false returns true. Also, when confronted with age > 64 & stillWorking, logical AND evaluates both subexpressions. This same pattern holds for logical exclusive OR and logical inclusive OR. Member Access Operator The member access operator (.) is used to access a class’s members or an object’s members. For example, String s = "Hello"; int len = s.length(); returns the length of the string assigned to variable s. It does so by calling the length() method member of the String class. Chapter 2 discusses this topic in more detail. Arrays are special objects that have a single length member. When you specify an array variable followed by the member access operator, followed by length, the resulting expression returns the number of elements in the array as a 32-bit integer. For example, ages.length returns the length of (the number of elements in) the array that ages references. Method Call Operator The method call operator—()—is used to signify that a method (discussed in Chapter 2) is being called. Furthermore, it identifies the number, order, and types of arguments that are passed to the method, to be picked up by the method’s parameters. System.out.println("Hello"); is an example. Multiplicative Operators The multiplicative operators consist of multiplication (*), division (/), and remainder (%). Multiplication returns the product of its operands (e.g., 6*4 returns 24), division returns the quotient of dividing its left operand by its right operand (e.g., 6/4 returns 1), and remainder returns the remainder of dividing its left operand by its right operand (e.g., 6%4 returns 2). The multiplication, division, and remainder operators can yield values that overflow or underflow the limits of the resulting value’s type. For example, multiplying two large positive 32-bit integer values can produce a value that cannot be represented as a 32-bit integer value. The result is said to overflow. Java does not detect overflows and underflows. Dividing a numeric value by 0 (via the division or remainder operator) also results in interesting behavior. Dividing an integer value by integer 0 causes the operator to throw an ArithmeticException 33 CHAPTER 1 GETTING STARTED WITH JAVA object (Chapter 3 covers exceptions). Dividing a floating-point/double precision floating-point value by 0 causes the operator to return +infinity or -infinity, depending on whether the dividend is positive or negative. Finally, dividing floating-point 0 by 0 causes the operator to return NaN (Not a Number). Object Creation Operator The object creation operator (new) creates an object from a class and also creates an array from an initializer. These topics are discussed in Chapter 2. Relational Operators The relational operators consist of relational greater than (>), relational greater than or equal to (>=), relational less than (<), relational less than or equal to (<=), and relational type checking (instanceof). The former four operators compare their operands and return true if the left operand is (respectively) greater than, greater than or equal to, less than, or less than or equal to the right operand. For example, each of 5.0 > 3, 2 >= 2, 16.1 < 303.3, and 54.0 <= 54.0 evaluates to true. The relational type-checking operator is used to determine whether an object belongs to a specific type. This topic is discussed in Chapter 2. Shift Operators The shift operators consist of left shift (<<), signed right shift (>>), and unsigned right shift (>>>). Left shift shifts the binary representation of its left operand leftward by the number of positions specified by its right operand. Each shift is equivalent to multiplying by 2. For example, 2 << 3 shifts 2’s binary representation left by 3 positions; the result is equivalent to multiplying 2 by 8. Each of signed and unsigned right shift shifts the binary representation of its left operand rightward by the number of positions specified by its right operand. Each shift is equivalent to dividing by 2. For example, 16 >> 3 shifts 16’s binary representation right by 3 positions; the result is equivalent to dividing 16 by 8. The difference between signed and unsigned right shift is what happens to the sign bit during the shift. Signed right shift includes the sign bit in the shift, whereas unsigned right shift ignores the sign bit. As a result, signed right shift preserved negative numbers, but unsigned right shift does not. For example, -4 >> 1 (the equivalent of -4/2) evaluates to -2, whereas –4 >>> 1 evaluates to 2147483646. Tip The shift operators are faster than multiplying or dividing by powers of 2. Unary Minus/Plus Operators Unary minus (-) and unary plus (+) are the simplest of all operators. Unary minus returns the negative of its operand (such as -5 returns -5 and --5 returns 5), whereas unary plus returns its operand verbatim (such as +5 returns 5 and +-5 returns -5). Unary plus is not commonly used, but is present for completeness. 34 CHAPTER 1 GETTING STARTED WITH JAVA Precedence and Associativity When evaluating a compound expression, Java takes each operator’s precedence (level of importance) into account to ensure that the expression evaluates as expected. For example, when presented with the expression 60+3*6, we expect multiplication to be performed before addition (multiplication has higher precedence than addition), and the final result to be 78. We do not expect addition to occur first, yielding a result of 378. Note Table 1-3’s rightmost column presents a value that indicates an operator’s precedence: the higher the number, the higher the precedence. For example, addition’s precedence level is 10 and multiplication’s precedence level is 11, which means that multiplication is performed before addition. Precedence can be circumvented by introducing open and close parentheses, ( and ), into the expression, where the innermost pair of nested parentheses is evaluated first. For example, 2*((60+3)*6) results in (60+3) being evaluated first, (60+3)*6 being evaluated next, and the overall expression being evaluated last. Similarly, in the expression 60/(3-6), subtraction is performed before division. During evaluation, operators with the same precedence level (e.g., addition and subtraction, which both have level 10) are processed according to their associativity (a property that determines how operators having the same precedence are grouped when parentheses are missing). For example, expression 9*4/3 is evaluated as if it was (9*4)/3 because * and / are left-to-right associative operators. In contrast, expression x=y=z=100 is evaluated as if it was x=(y=(z=100))—100 is assigned to z, z’s new value (100) is assigned to y, and y’s new value (100) is assigned to x – because = is a right-to-left associative operator. Most of Java’s operators are left-to-right associative. Right-to-left associative operators include assignment, bitwise complement, cast, compound assignment, conditional, logical complement, object creation, predecrement, preincrement, unary minus, and unary plus. Note Unlike languages such as C++, Java doesn’t let you overload operators. However, Java overloads the +, ++, and -- operator symbols. Statements Statements are the workhorses of a program. They assign values to variables, control a program’s flow by making decisions and/or repeatedly executing other statements, and perform other tasks. A statement can be expressed as a simple statement or as a compound statement: • A simple statement is a single standalone source code instruction for performing some task; it’s terminated with a semicolon. 35 CHAPTER 1 GETTING STARTED WITH JAVA • A compound statement is a (possibly empty) sequence of simple and other compound statements sandwiched between open and close brace delimiters—a delimiter is a character that marks the beginning or end of some section. A method body (e.g., the main() method’s body) is an example. Compound statements can appear wherever simple statements appear and are alternatively referred to as blocks. This section introduces you to many of Java’s statements. Additional statements are covered in later chapters. For example, Chapter 2 discusses the return statement. Assignment Statements The assignment statement is an expression that assigns a value to a variable. This statement begins with a variable name, continues with the assignment operator (=) or a compound assignment operator (such as +=), and concludes with an expression and a semicolon. Below are three examples: x = 10; ages[0] = 25; counter += 10; The first example assigns integer 10 to variable x, which is presumably of type integer as well. The second example assigns integer 25 to the first element of the ages array. The third example adds 10 to the value stored in counter and stores the sum in counter. Note Initializing a variable in the variable’s declaration (e.g., int counter = 1;) can be thought of as a special form of the assignment statement. Decision Statements The previously described conditional operator (?:) is useful for choosing between two expressions to evaluate, and cannot be used to choose between two statements. For this purpose, Java supplies three decision statements: if, if-else, and switch. If Statement The if statement evaluates a Boolean expression and executes another statement when this expression evaluates to true. This statement has the following syntax: if (Boolean expression) statement If consists of reserved word if, followed by a Boolean expression in parentheses, followed by a statement to execute when Boolean expression evaluates to true. The following example demonstrates this statement: if (numMonthlySales > 100) wage += bonus; 36 CHAPTER 1 GETTING STARTED WITH JAVA If the number of monthly sales exceeds 100, numMonthlySales > 100 evaluates to true and the wage += bonus; assignment statement executes. Otherwise, this assignment statement does not execute. If-Else Statement The if-else statement evaluates a Boolean expression and executes one of two statements depending on whether this expression evaluates to true or false. This statement has the following syntax: if (Boolean expression) statement1 else statement2 If-else consists of reserved word if, followed by a Boolean expression in parentheses, followed by a statement1 to execute when Boolean expression evaluates to true, followed by a statement2 to execute when Boolean expression evaluates to false. The following example demonstrates this statement: if ((n&1) == 1) System.out.println("odd"); else System.out.println("even"); This example assumes the existence of an int variable named n that has been initialized to an integer. It then proceeds to determine whether the integer is odd (not divisible by 2) or even (divisible by 2). The Boolean expression first evaluates n&1, which bitwise ANDs n’s value with 1. It then compares the result to 1. If they are equal, a message stating that n’s value is odd outputs; otherwise, a message stating that n’s value is even outputs. The parentheses are required because == has higher precedence than &. Without these parentheses, the expression’s evaluation order would change to first evaluating 1 == 1 and then trying to bitwise AND the Boolean result with n’s integer value. This order results in a compiler error message because of a type mismatch: you cannot bitwise AND an integer with a Boolean value. You could rewrite this if-else statement example to use the conditional operator, as follows: System.out.println((n&1) == 1 ? "odd" : "even");. However, you cannot do so with the following example: if ((n&1) == 1) odd(); else even(); This example assumes the existence of odd() and even() methods that don’t return anything. Because the conditional operator requires that each of its second and third operands evaluates to a value, the compiler reports an error when attempting to compile (n&1) == 1 ? odd() : even(). You can chain multiple if-else statements together, resulting in the following syntax: if (Boolean expression1) statement1 else if (Boolean expression2) statement2 else 37 CHAPTER 1 GETTING STARTED WITH JAVA … else statementN If Boolean expression1 evaluates to true, statement1 executes. Otherwise, if Boolean expression2 evaluates to true, statement2 executes. This pattern continues until one of these expressions evaluates to true and its corresponding statement executes, or the final else is reached and statementN (the default statement) executes. The following example demonstrates this chaining: if (testMark >= 90) { gradeLetter = 'A'; System.out.println("You aced the test."); } else if (testMark >= 80) { gradeLetter = 'B'; System.out.println("You did very well on this test."); } else if (testMark >= 70) { gradeLetter = 'C'; System.out.println("Not bad, but you need to study more for future tests."); } else if (testMark >= 60) { gradeLetter = 'D'; System.out.println("Your test result suggests that you need a tutor."); else { gradeLetter = 'F'; System.out.println("Your test result is pathetic; you need summer school."); } DANGLING-ELSE PROBLEM When if and if-else are used together, and the source code is not properly indented, it can be difficult to determine which if associates with the else. For example: if (car.door.isOpen()) if (car.key.isPresent()) car.start(); else car.door.open(); 38 CHAPTER 1 GETTING STARTED WITH JAVA Did the developer intend for the else to match the inner if, but improperly formatted the code to make it appear otherwise? For example: if (car.door.isOpen()) if (car.key.isPresent()) car.start(); else car.door.open(); If car.door.isOpen() and car.key.isPresent() each return true, car.start() executes. If car.door.isOpen() returns true and car.key.isPresent() returns false, car.door.open(); executes. Attempting to open an open door makes no sense. This book was purchased by [email protected] The developer must have wanted the else to match the outer if, but forgot that else matches the nearest if. This problem can be fixed by surrounding the inner if with braces, as follows: if (car.door.isOpen()) { if (car.key.isPresent()) car.start(); } else car.door.open(); When car.door.isOpen() returns true, the compound statement executes. When this method returns false, car.door.open(); executes, which makes sense. Forgetting that else matches the nearest if and using poor indentation to obscure this fact is known as the dangling-else problem. Switch Statement The switch statement lets you choose from among several execution paths in a more efficient manner than with equivalent chained if-else statements. This statement has the following syntax: switch (selector expression) { case value1: statement1 [break;] case value2: statement2 [break;] … case valueN: statementN [break;] [default: statement] } Switch consists of reserved word switch, followed by a selector expression in parentheses, followed by a body of cases. The selector expression is any expression that evaluates to an integer, character, or string value. For example, it might evaluate to a 32-bit integer or to a 16-bit character. 39 CHAPTER 1 GETTING STARTED WITH JAVA Each case begins with reserved word case, continues with a literal value and a colon character (:), continues with a statement to execute, and optionally concludes with a break statement, which causes execution to continue after the switch statement. After evaluating the selector expression, switch compares this value with each case’s value until it finds a match. If there is a match, the case’s statement is executed. For example, if the selector expression’s value matches value1, statement1 executes. The optional break statement (anything placed in square brackets is optional), which consists of reserved word break followed by a semicolon, prevents the flow of execution from continuing with the next case’s statement. Instead, execution continues with the first statement following switch. Note You will usually place a break statement after a case’s statement. Forgetting to include break can lead to a hard-to-find bug. However, there are situations where you want to group several cases together and have them execute common code. In such a situation, you would omit the break statement from the participating cases. If none of the cases’ values match the selector expression’s value, and if a default case (signified by the default reserved word followed by a colon) is present, the default case’s statement is executed. The following example demonstrates this statement: switch (direction) { case 0: System.out.println("You are travelling case 1: System.out.println("You are travelling case 2: System.out.println("You are travelling case 3: System.out.println("You are travelling default: System.out.println("You are lost."); } north."); break; east."); break; south."); break; west."); break; This example assumes that direction stores an integer value. If this value is in the range 0-3, an appropriate direction message is output; otherwise, a message about being lost is output. Note This example hardcodes values 0, 1, 2, and 3, which is not a good idea in practice. Instead, constants should be used. Chapter 2 introduces you to constants. Loop Statements It’s often necessary to repeatedly execute a statement, and this repeated execution is called a loop. Java provides three kinds of loop statements: for, while, and do-while. This section first discusses these statements. It then examines the topic of looping over the empty statement. Finally, the section discusses the break, labeled break, continue, and labeled continue statements for prematurely ending all or part of a loop. 40 CHAPTER 1 GETTING STARTED WITH JAVA For Statement The for statement lets you loop over a statement a specific number of times, or even indefinitely. This statement has the following syntax: for ([initialize]; [test]; [update]) statement For consists of reserved word for, followed by a header in parentheses, followed by a statement to execute. The header consists of an optional initialize section, followed by an optional test section, followed by an optional update section. A nonoptional semicolon separates each of the first two sections from the next section. The initialize section consists of a comma-separated list of variable declarations or variable assignments. Some or all of these variables are typically used to control the loop’s duration, and are known as loop-control variables. The test section consists of a Boolean expression that determines how long the loop executes. Execution continues as long as this expression evaluates to true. Finally, the update section consists of a comma-separated list of expressions that typically modify the loop-control variables. For is perfect for iterating (looping) over an array. Each iteration (loop execution) accesses one of the array’s elements via an array[index] expression, where array is the array whose element is being accessed, and index is the zero-based location of the element being accessed. The following example uses the for statement to iterate over the array of command-line arguments that is passed to the main() method: public static void main(String[] args) { for (int i = 0; i < args.length; i++) switch (args[i]) { case "-v": case "-V": System.out.println("version 1.0"); break; default : showUsage(); } } For’s initialization section declares variable i for controlling the loop, its test section compares i’s current value to the length of the args array to ensure that this value is less than the array’s length, and its update section increments i by 1. The loop continues until i’s value equals the array’s length. Each iteration accesses one of the array’s values via the args[i] expression. This expression returns this array’s ith value (which happens to be a String object in this example). The first value is stored in args[0]. The args[i] expression serves as the switch statement’s selector expression. If this String object contains -V, the second case is executed, which calls System.out.println() to output a version number message. The subsequent break statement keeps execution from falling into the default case, which calls showUsage() to output usage information when main() is called with unexpected arguments. If this String object contains -v, the lack of a break statement following the first case causes execution to fall through to the second case, calling System.out.println(). This example demonstrates the occasional need to group cases to execute common code. 41 CHAPTER 1 GETTING STARTED WITH JAVA Note Although I’ve named the array containing command-line arguments args, this name isn’t mandatory. I could as easily have named it arguments (or even some_other_name). The following example uses the for statement to output the contents of the previously declared matrix array, which is redeclared here for convenience: float[][] matrix = { { 1.0F, 2.0F, 3.0F }, { 4.0F, 5.0F, 6.0F }}; for (int row = 0; row < matrix.length; row++) { for (int col = 0; col < matrix[row].length; col++) System.out.print(matrix[row][col]+" "); System.out.print("\n"); } Expression matrix.length returns the number of rows in this tabular array. For each row, expression matrix[row].length returns the number of columns for that row. This latter expression suggests that each row can have a different number of columns, although each row has the same number of columns in the example. System.out.print() is closely related to System.out.println(). Unlike the latter method, System.out.print() outputs its argument without a trailing newline. This example generates the following output: 1.0 2.0 3.0 4.0 5.0 6.0 While Statement The while statement repeatedly executes a statement while its Boolean expression evaluates to true. This statement has the following syntax: while (Boolean expression) statement While consists of reserved word while, followed by a parenthesized Boolean expression header, followed by a statement to repeatedly execute. The while statement first evaluates the Boolean expression. If it is true, while executes the other statement. Once again, the Boolean expression is evaluated. If it is still true, while re-executes the statement. This cyclic pattern continues. Prompting the user to enter a specific character is one situation where while is useful. For example, suppose that you want to prompt the user to enter a specific uppercase letter or its lowercase equivalent. The following example provides a demonstration: int ch = 0; while (ch != 'C' && ch != 'c') { System.out.println("Press C or c to continue."); ch = System.in.read(); } 42 CHAPTER 1 GETTING STARTED WITH JAVA This example begins by initializing variable ch. This variable must be initialized; otherwise, the compiler will report an uninitialized variable when it tries to read ch’s value in the while statement’s Boolean expression. This expression uses the conditional AND operator (&&) to test ch’s value. This operator first evaluates its left operand, which happens to be expression ch != 'C'. (The != operator converts 'C' from 16-bit unsigned char type to 32-bit signed int type prior to the comparison.) If ch does not contain C (it does not at this point—0 was just assigned to ch), this expression evaluates to true. The && operator next evaluates its right operand, which happens to be expression ch != 'c'. Because this expression also evaluates to true, conditional AND returns true and while executes the compound statement. The compound statement first outputs, via the System.out.println() method call, a message that prompts the user to press the C key with or without the Shift key. It next reads the entered keystroke via System.in.read(), saving its integer value in ch. From left to right, System identifies a standard class of system utilities, in identifies an object located in System that provides methods for inputting one or more bytes from the standard input device, and read() returns the next byte (or -1 when there are no more bytes). Following this assignment, the compound statement ends and while re-evaluates its Boolean expression. Suppose ch contains C’s integer value. Conditional AND evaluates ch != 'C', which evaluates to false. Seeing that the expression is already false, conditional AND short circuits its evaluation by not evaluating its right operand, and returns false. The while statement subsequently detects this value and terminates. Suppose ch contains c’s integer value. Conditional AND evaluates ch != 'C', which evaluates to true. Seeing that the expression is true, conditional AND evaluates ch != 'c', which evaluates to false. Once again, the while statement terminates. 43 CHAPTER 1 GETTING STARTED WITH JAVA Note A for statement can be coded as a while statement. For example, for (int i = 0; i < 10; i++) System.out.println(i); is equivalent to int i = 0; while (i < 10) { System.out.println(i); i++; } Do-While Statement The do-while statement repeatedly executes a statement while its Boolean expression evaluates to true. Unlike the while statement, which evaluates the Boolean expression at the top of the loop, do-while evaluates the Boolean expression at the bottom of the loop. This statement has the following syntax: do statement while(Boolean expression); Do-while consists of the do reserved word, followed by a statement to repeatedly execute, followed by the while reserved word, followed by a parenthesized Boolean expression header, followed by a semicolon. The do-while statement first executes the other statement. It then evaluates the Boolean expression. If it is true, do-while executes the other statement. Once again, the Boolean expression is evaluated. If it is still true, do-while re-executes the statement. This cyclic pattern continues. The following example demonstrates do-while prompting the user to enter a specific uppercase letter or its lowercase equivalent: int ch; do { System.out.println("Press C or c to continue."); ch = System.in.read(); } while (ch != 'C' && ch != 'c'); 44 CHAPTER 1 GETTING STARTED WITH JAVA This example is similar to its predecessor. Because the compound statement is no longer executed prior to the test, it’s no longer necessary to initialize ch – ch is assigned System.in.read()’s return value prior to the Boolean expression’s evaluation. Looping Over the Empty Statement Java refers to a semicolon character appearing by itself as the empty statement. It’s sometimes convenient for a loop statement to execute the empty statement repeatedly. The actual work performed by the loop statement takes place in the statement header. Consider the following example: for (String line; (line = readLine()) != null; System.out.println(line)); This example uses for to present a programming idiom for copying lines of text that are read from some source, via the fictitious readLine() method in this example, to some destination, via System.out.println() in this example. Copying continues until readLine() returns null. Note the semicolon (empty statement) at the end of the line. Caution Be careful with the empty statement because it can introduce subtle bugs into your code. For example, the following loop is supposed to output the string Hello on ten lines. Instead, only one instance of this string is output, because it is the empty statement and not System.out.println() that’s executed ten times: for (int i = 0; i < 10; i++); // this ; represents the empty statement System.out.println("Hello"); Break and Labeled Break Statements What do for(;;);, while(true); and do;while(true); have in common? Each of these loop statements presents an extreme example of an infinite loop (a loop that never ends). An infinite loop is something that you should avoid because its unending execution causes your application to hang, which is not desirable from the point of view of your application’s users. Caution An infinite loop can also arise from a loop header’s Boolean expression comparing a floating-point value against a nonzero value via the equality or inequality operator, because many floating-point values have inexact internal representations. For example, the following code fragment never ends because 0.1 does not have an exact internal representation: for (double d = 0.0; d != 1.0; d += 0.1) System.out.println(d); 45 CHAPTER 1 GETTING STARTED WITH JAVA However, there are times when it is handy to code a loop as if it were infinite by using one of the aforementioned programming idioms. For example, you might code a while(true) loop that repeatedly prompts for a specific keystroke until the correct key is pressed. When the correct key is pressed, the loop must end. Java provides the break statement for this purpose. The break statement transfers execution to the first statement following a switch statement (as discussed earlier) or a loop. In either scenario, this statement consists of reserved word break followed by a semicolon. The following example uses break with an if decision statement to exit a while(true)-based infinite loop when the user presses the C or c key: int ch; while (true) { System.out.println("Press C or c to continue."); ch = System.in.read(); if (ch == 'C' || ch == 'c') break; } The break statement is also useful in the context of a finite loop. For example, consider a scenario where an array of values is searched for a specific value, and you want to exit the loop when this value is found. The following example reveals this scenario: int[] employeeIDs = { 123, 854, 567, 912, 224 }; int employeeSearchID = 912; boolean found = false; for (int i = 0; i < employeeIDs.length; i++) if (employeeSearchID == employeeIDs[i]) { found = true; break; } System.out.println((found) ? "employee "+employeeSearchID+" exists" : "no employee ID matches "+employeeSearchID); The example uses for and if to search an array of employee IDs to determine whether a specific employee ID exists. If this ID is found, if’s compound statement assigns true to found. Because there is no point in continuing the search, it then uses break to quit the loop. The labeled break statement transfers execution to the first statement following the loop that’s prefixed by a label (an identifier followed by a colon). It consists of reserved word break, followed by an identifier for which the matching label must exist. Furthermore, the label must immediately precede a loop statement. Labeled break is useful for breaking out of nested loops (loops within loops). The following example reveals the labeled break statement transferring execution to the first statement that follows the outer for loop: 46 CHAPTER 1 GETTING STARTED WITH JAVA outer: for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) if (i == 1 && j == 1) break outer; else System.out.println("i="+i+", j="+j); System.out.println("Both loops terminated."); When i’s value is 1 and j’s value is 1, break outer; is executed to terminate both for loops. This statement transfers execution to the first statement after the outer for loop, which happens to be System.out.println("Both loops terminated.");. The following output is generated: i=0, i=0, i=0, i=1, Both j=0 j=1 j=2 j=0 loops terminated. Continue and Labeled Continue Statements The continue statement skips the remainder of the current loop iteration, re-evaluates the header’s Boolean expression, and performs another iteration (if true) or terminates the loop (if false). Continue consists of reserved word continue followed by a semicolon. Consider a while loop that reads lines from a source and processes nonblank lines in some manner. Because it should not process blank lines, while skips the current iteration when a blank line is detected, as demonstrated in the following example: String line; while ((line = readLine()) != null) { if (isBlank(line)) continue; processLine(line); } This example employs a fictitious isBlank() method to determine whether the currently read line is blank. If this method returns true, if executes the continue statement to skip the rest of the current iteration and read the next line whenever a blank line is detected. Otherwise, the fictitious processLine() method is called to process the line’s contents. Look carefully at this example and you should realize that the continue statement is not needed. Instead, this listing can be shortened via refactoring (rewriting source code to improve its readability, organization, or reusability), as demonstrated in the following example: String line; while ((line = readLine()) != null) { if (!isBlank(line)) processLine(line); } 47 CHAPTER 1 GETTING STARTED WITH JAVA This example’s refactoring modifies if’s Boolean expression to use the logical complement operator (!). Whenever isBlank() returns false, this operator flips this value to true and if executes processLine(). Although continue isn’t necessary in this example, you’ll find it convenient to use this statement in more complex code where refactoring isn’t as easy to perform. The labeled continue statement skips the remaining iterations of one or more nested loops and transfers execution to the labeled loop. It consists of reserved word continue, followed by an identifier for which a matching label must exist. Furthermore, the label must immediately precede a loop statement. Labeled continue is useful for breaking out of nested loops while still continuing to execute the labeled loop. The following example reveals the labeled continue statement terminating the inner for loop’s iterations: outer: for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) if (i == 1 && j == 1) continue outer; else System.out.println("i="+i+", j="+j); System.out.println("Both loops terminated."); When i’s value is 1 and j’s value is 1, continue outer; is executed to terminate the inner for loop and continue with the outer for loop at its next value of i. Both loops continue until they finish. The following output is generated: i=0, i=0, i=0, i=1, i=2, i=2, i=2, Both j=0 j=1 j=2 j=0 j=0 j=1 j=2 loops terminated. EXERCISES The following exercises are designed to test your understanding of applications and language fundamentals: 48 1. Declare an EchoArgs class whose main() method outputs its command-line arguments, one argument per line. Store this class in a file named EchoArgs.java. Compile this source code (javac EchoArgs.java) and run the application; for example, java EchoArgs A B C. You should see each of A, B, and C appearing on a separate line. 2. Declare a Circle class whose main() method declares a double precision floating-point variable named PI that’s initialized to 3.14159, declares a double precision floating-point variable named radius that’s initialized to 15, calculates and outputs the circle’s circumference (PI times the diameter), and calculates and CHAPTER 1 GETTING STARTED WITH JAVA outputs the circle’s area (PI times the square of the radius). Compile and run this application. 3. Declare an Input class whose main() method is declared as follows: public static void main(String[] args) throws java.io.IOException—don’t worry about throws java.io.IOException; you’ll learn about this language feature in Chapter 3. Continuing, insert the “loop until C or c is input” example from the “Break and Labeled Break Statements” section into the main() method. Compile and run this application. When prompted, type a key and press the Enter/Return key. What happens when you type multiple keys (abc, for example) and press Enter/Return? This book was purchased by [email protected] 4. Declare a Triangle class whose main() method uses a pair of nested for statements along with System.out.print() to output a 10-row triangle of asterisks, where each row contains an odd number of asterisks (1, 3, 5, 7, and so on), as follows: * *** ***** ******* ********* *********** ************* *************** ***************** ******************* Compile and run this application. 5. Declare an OutputReversedInt class whose main() method declares an int variable named x that’s assigned a positive integer. This declaration is followed by a while loop that outputs this integer’s digits in reverse. For example, 876432094 outputs as 490234678. Summary Java is a language for describing programs. This general-purpose, class-based, and object-oriented language is patterned after C and C++ to make it easier for existing C/C++ developers to migrate to Java. Java is also a platform on which to run programs written in Java and other languages (e.g., Groovy, Jython, and JRuby). Unlike platforms with physical processors (e.g., an Intel processor) and operating systems (e.g., Windows 7), the Java platform consists of a virtual machine and execution environment. Before you can develop Java programs, you need to determine what kind(s) of programs you want to develop and then install the appropriate software. Use the JDK to develop standalone applications and applets, the Java ME SDK to develop MIDlets and Xlets, and the Java EE SDK to develop servlets and JSPs. For small projects, it’s no big deal to work at the command line with JDK tools. Because you’ll probably find this scenario tedious (and even unworkable) for larger projects, you should also consider obtaining an IDE such as NetBeans 7, which includes support for those language features introduced by JDK 7. 49 CHAPTER 1 GETTING STARTED WITH JAVA Most computer languages support comments, identifiers, types, variables, expressions, and statements. Comments let you document your source code; identifiers name things (e.g., classes and methods); types identify sets of values (and their representations in memory) and sets of operations that transform these values into other values of that set; variables store values; expressions combine variables, method calls, literals, and operators; and statements are the workhorses of a program, and include assignment, decision, loop, break and labeled break, and continue and labeled continue. Now that you possess a basic understanding of Java’s fundamental language features, you’re ready to learn about Java’s language support for classes and objects. Chapter 2 introduces you to this support. 50 CHAPTER 2 Discovering Classes and Objects Chapter 1 gently introduced you to the Java language by focusing mainly on fundamental language features ranging from comments to statements. Using only these features, you can create simple applications (such as HelloWorld and the applications mentioned in the chapter’s exercises) that are reminiscent of those written in structured programming languages such as C. ■ Note Structured programming is a programming paradigm that enforces a logical structure on programs through data structures (named aggregates of data items), functions (named blocks of code that return values to the code that calls [passes program execution to] them), and procedures (named blocks of code that don’t return values to their callers). Structured programs use sequence (one statement follows another statement), selection/choice (if/switch), and repetition/iteration (for/while/do) programming constructs; use of the potentially harmful GOTO statement (see http://en.wikipedia.org/wiki/GOTO) is discouraged. Structured programs separate data from behaviors. This separation makes it difficult to model realworld entities (such as a bank accounts and employees) and often leads to maintenance headaches when programs become complex. In contrast, classes and objects combine data and behaviors into program entities; programs based on classes and objects are typically easier to understand and maintain. Chapter 2 takes you deeper into the Java language by focusing on its support for classes and objects. You first learn how to declare classes and create objects from these classes, and then learn how to encapsulate state and behaviors into these program entities through fields and methods. After learning about class and object initialization, you move beyond this object-based programming model and dive into object-oriented programming, by exploring Java’s inheritance- and polymorphism-oriented language features. At this point, the chapter presents one of Java’s more confusing language features: interfaces. You learn what interfaces are, how they relate to classes, and what makes them so useful. Java programs create objects that occupy memory. To reduce the possibility of running out of memory, the Java Virtual Machine (JVM)’s garbage collector occasionally performs garbage collection by locating objects that are no longer being used and removing this garbage to free up memory. Chapter 2 concludes by introducing you to the garbage collection process. 51 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Declaring Classes and Creating Objects Structured programs create data structures that organize and store data items, and manipulate the data stored in these data structures via functions and procedures. The fundamental units of a structured program are its data structures and the functions or procedures that manipulate them. Although Java lets you create applications in a similar fashion, this language is really about declaring classes and creating objects from these classes. These program entities are the fundamental units of a Java program. This section first shows you how to declare a class, and then shows you how to create objects from this class with the help of the new operator and a constructor. The section then shows you how to specify constructor parameters and local variables. Finally, you learn how to create arrays using the same new operator that’s used to create an object from a class. Declaring Classes A class is a template for manufacturing objects (named aggregates of code and data), which are also known as class instances, or instances for short. Classes generalize real-world entities, and objects are specific manifestations of these entities at the program level. You might think of classes as cookie cutters and objects as the cookies that cookie cutters create. Because you cannot instantiate objects from a class that does not exist, you must first declare the class. The declaration consists of a header followed by a body. At minimum, the header consists of reserved word class followed by a name that identifies the class (so that it can be referred to from elsewhere in the source code). The body starts with an open brace character ({) and ends with a close brace (}). Sandwiched between these delimiters are various kinds of declarations. Consider Listing 2-1. Listing 2-1. Declaring a skeletal Image class class Image { // various member declarations } Listing 2-1 declares a class named Image, which presumably describes some kind of image for displaying on the screen. By convention, a class’s name begins with an uppercase letter. Furthermore, the first letter of each subsequent word in a multiword class name is capitalized. This is known as camelcasing. Creating Objects with the new Operator and a Constructor Image is an example of a user-defined type from which objects can be created. You create these objects by using the new operator with a constructor, as follows: Image image = new Image(); The new operator allocates memory to store the object whose type is specified by new’s solitary operand, which happens to be Image() in this example. The object is stored in a region of memory known as the heap. The parentheses (round brackets) that follow Image signify a constructor, which is a block of code for constructing an object by initializing it in some manner. The new operator invokes (calls) the constructor immediately after allocating memory to store the object. When the constructor ends, new returns a reference (a memory address or other identifier) to the object so that it can be accessed elsewhere in the program. Regarding the newly created Image object, its 52 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS reference is stored in a variable named image whose type is specified as Image. (It’s common to refer to the variable as an object, as in the image object, although it stores only an object’s reference and not the object itself.) ■ Note new’s returned reference is represented in source code by keyword this. Wherever this appears, it represents the current object. Also, variables that store references are called reference variables. Image does not explicitly declare a constructor. When a class does not declare a constructor, Java implicitly creates a constructor for that class. The created constructor is known as the default noargument constructor because no arguments (discussed shortly) appear between its ( and ) characters when the constructor is invoked. ■ Note Java does not create a default noargument constructor when at least one constructor is declared. Specifying Constructor Parameters and Local Variables You explicitly declare a constructor within a class’s body by specifying the name of the class followed by a parameter list, which is a round bracket-delimited and comma-separated list of zero or more parameter declarations. A parameter is a constructor or method variable that receives an expression value passed to the constructor or method when it is called. This expression value is known as an argument. Listing 2-2 enhances Listing 2-1’s Image class by declaring three constructors with parameter lists that declare zero, one, or two parameters; and a main() method for testing this class. Listing 2-2. Declaring an Image class with three constructors and a main() method class Image { Image() { System.out.println("Image() called"); } Image(String filename) { this(filename, null); System.out.println("Image(String filename) called"); } Image(String filename, String imageType) { System.out.println("Image(String filename, String imageType) called"); if (filename != null) { System.out.println("reading "+filename); 53 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS if (imageType != null) System.out.println("interpreting "+filename+" as storing a "+ imageType+" image"); } // Perform other initialization here. } public static void main(String[] args) { Image image = new Image(); System.out.println(); image = new Image("image.png"); System.out.println(); image = new Image("image.png", "PNG"); } } Listing 2-2’s Image class first declares a noargument constructor for initializing an Image object to default values (whatever they may be). This constructor simulates default initialization by invoking System.out.println() to output a message signifying that it’s been called. Image next declares an Image(String filename) constructor whose parameter list consists of a single parameter declaration—a parameter declaration consists of a variable’s type followed by the variable’s name. The java.lang.String parameter is named filename, signifying that this constructor obtains image content from a file. ■ Note Throughout this book’s chapters, I typically prefix the first use of a predefined type (such as String) with the package hierarchy in which the type is stored. For example, String is stored in the lang subpackage of the java package. I do so to help you learn where types are stored so that you can more easily specify import statements for importing these types (without having to first search for a type’s package) into your source code— you don’t have to import types that are stored in the java.lang package, but I still prefix the java.lang package to the type name for completeness. I will have more to say about packages and the import statement in Chapter 3. Some constructors rely on other constructors to help them initialize their objects. This is done to avoid redundant code, which increases the size of an object, and unnecessarily takes memory away from the heap that could be used for other purposes. For example, Image(String filename) relies on Image(String filename, String imageType) to read the file’s image content into memory. Although it appears otherwise, constructors don’t have names (although it is common to refer to a constructor by specifying the class name and parameter list). A constructor calls another constructor by using keyword this and a round bracket-delimited and comma-separated list of arguments. For example, Image(String filename) executes this(filename, null); to execute Image(String filename, String imageType). 54 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Caution You must use this to call another constructor—you cannot use the class’s name, as in Image(). The this() constructor call (if present) must be the first code that is executed within the constructor. This rule prevents you from specifying multiple this() constructor calls in the same constructor. Finally, you cannot specify this() in a method—constructors can be called only by other constructors and during object creation. (I will discuss methods later in this chapter.) When present, the constructor call must be the first code that is specified within a constructor; otherwise, the compiler reports an error. For this reason, a constructor that calls another constructor can only perform additional work after the other constructor has finished. For example, Image(String filename) executes System.out.println("Image(String filename) called"); after the invoked Image(String filename, String imageType) constructor finishes. The Image(String filename, String imageType) constructor declares an imageType parameter that signifies the kind of image stored in the file—a Portable Network Graphics (PNG) image, for example. Presumably, the constructor uses imageType to speed up processing by not examining the file’s contents to learn the image format. When null is passed to imageType, as happens with the Image(String filename) constructor, Image(String filename, String imageType) examines file contents to learn the format. If null was also passed to filename, Image(String filename, String imageType) wouldn’t read the file, but would presumably notify the code attempting to create the Image object of an error condition. After declaring the constructors, Listing 2-2 declares a main() method that lets you create Image objects and view output messages. main() creates three Image objects, calling the first constructor with no arguments, the second constructor with argument "image.png", and the third constructor with arguments "image.png" and "PNG". ■ Note The number of arguments passed to a constructor or method, or the number of operator operands is known as the constructor’s, method’s, or operator’s arity. Each object’s reference is assigned to a reference variable named image, replacing the previously stored reference for the second and third object assignments. (Each occurrence of System.out.println(); outputs a blank line to make the output easier to read.) The presence of main() changes Image from only a class to an application. You typically place main() in classes that are used to create objects in order to test such classes. When constructing an application for use by others, you usually declare main() in a class where the intent is to run an application and not to create an object from that class—the application is then run from only that class. See Chapter 1’s HelloWorld class for an example. After saving Listing 2-2 to Image.java, compile this file by executing javac Image.java at the command line. Assuming that there are no error messages, execute the application by specifying java Image. You should observe the following output: Image() called Image(String filename, String imageType) called 55 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS reading image.png Image(String filename) called Image(String filename, String imageType) called reading image.png interpreting image.png as storing a PNG image The first output line indicates that the noargument constructor has been called. Subsequent output lines indicate that the second and third constructors have been called. In addition to declaring parameters, a constructor can declare variables within its body to help it perform various tasks. For example, the previously presented Image(String filename, String imageType) constructor might create an object from a (hypothetical) File class that provides the means to read a file’s contents. At some point, the constructor instantiates this class and assigns the instance’s reference to a variable, as demonstrated in the following: Image(String filename, String imageType) { System.out.println("Image(String filename, String imageType) called"); if (filename != null) { System.out.println("reading "+filename); File file = new File(filename); // Read file contents into object. if (imageType != null) System.out.println("interpreting "+filename+" as storing a "+ imageType+" image"); else // Inspect image contents to learn image type. ; // Empty statement is used to make if-else syntactically valid. } // Perform other initialization here. } As with the filename and imageType parameters, file is a variable that is local to the constructor, and is known as a local variable to distinguish it from a parameter. Although all three variables are local to the constructor, there are two key differences between parameters and local variables: 56 • The filename and imageType parameters come into existence at the point where the constructor begins to execute and exist until execution leaves the constructor. In contrast, file comes into existence at its point of declaration and continues to exist until the block in which it is declared is terminated (via a closing brace character). This property of a parameter or a local variable is known as lifetime. • The filename and imageType parameters can be accessed from anywhere in the constructor. In contrast, file can be accessed only from its point of declaration to the end of the block in which it is declared. It cannot be accessed before its declaration or after its declaring block, but nested subblocks can access the local variable. This property of a parameter or a local variable is known as scope. CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Note The lifetime and scope (also known as visibility) properties also apply to classes, objects, and fields (discussed later). Classes come into existence when loaded into memory and cease to exist when unloaded from memory, typically when an application exits. Also, loaded classes are typically visible to other classes, but this isn’t always the case—Appendix C will have more to say about this issue when it presents classloaders. An object’s lifetime ranges from its creation via the new operator until the moment when it is removed from memory by the garbage collector. Its scope depends on various factors, such as when its reference is assigned to a local variable or to a field. I discuss fields later in this chapter. The lifetime of a field depends upon whether it is an instance field or a class field. If the field belongs to an object, it comes into existence when the object is created and dies when the object disappears from memory. If the field belongs to a class, the field begins its existence when the class is loaded and disappears when the class is removed from memory. As with an object, a field’s scope depends upon various factors, such as whether the field is declared to have private access or not—you’ll learn about private access later in this chapter. A local variable cannot have the same name as a parameter because a parameter always has the same scope as the local variable. However, a local variable can have the same name as another local variable provided that both variables are located within different scopes (that is, within different blocks). For example, you could specify int x = 1; within an if-else statement’s if block and specify double x = 2.0; within the statement’s corresponding else block, and each local variable would be distinct. ■ Note The discussion of constructor parameters, arguments, and local variables also applies to method parameters, arguments, and local variables—I discuss methods later in this chapter. Creating Arrays with the new Operator The new operator is also used to create an array of objects in the heap, and is an alternative to the array initializer presented in Chapter 1. ■ Note An array is implemented as a special Java object whose read-only length field contains the array’s size (the number of elements). You’ll learn about fields later in this chapter. 57 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS When creating the array, specify new followed by a name that identifies the type of values that are stored in the array, followed by one or more pairs of square brackets that signify the number of dimensions occupied by the array. The leftmost pair of square brackets must contain an integral expression that specifies the size of the array (the number of elements), whereas remaining pairs contain integral expressions or are empty. For example, you can use new to create a one-dimensional array of object references, as demonstrated by the following example, which creates a one-dimensional array that can store ten Image object references: Image[] imArray = new Image[10]; When you create a one-dimensional array, new zeros the bits in each array element’s storage location, which you interpret at the source code level as literal value false, '\u0000', 0, 0L, 0.0, 0.0F, or null (depending on element type). In the previous example, each of imArray’s elements is initialized to null, which represents the null reference (a reference to no object). After creating an array, you need to assign object references to its elements. The following example demonstrates this task by creating Image objects and assigning their references to imArray elements: for (int i = 0; i < imArray.length; i++) imArray[i] = new Image("image"+i+".png"); // image0.png, image1.png, and so on The "image"+i+".png" expression uses the string concatenation operator (+) to combine image with the string equivalent of the integer value stored in variable i with .png. The resulting string is passed to Image’s Image(String filename) constructor. ■ Caution Use of the string concatenation operator in a loop context can result in a lot of unnecessary String object creation, depending on the length of the loop. I will discuss this topic in Chapter 4 when I introduce you to the String class. You can also use new to create arrays of primitive type values (such as integers or double precision floating-point numbers). For example, suppose you want to create a two-dimensional three-row-bytwo-column array of double precision floating-point temperature values. The following example accomplishes this task: double[][] temperatures = new double[3][2]; After creating a two-dimensional array, you will want to populate its elements with suitable values. The following example initializes each temperatures element, which is accessed as temperatures[row][col], to a randomly generated temperature value via Math.random(), which I’ll explain in Chapter 4: for (int row = 0; row < temperatures.length; row++) for (int col = 0; col < temperatures[row].length; col++) temperatures[row][col] = Math.random()*100; You can subsequently output these values in a tabular format by using a for loop, as demonstrated by the following example—the code makes no attempt to align the temperature values in perfect columns: for (int row = 0; row < temperatures.length; row++) 58 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS { for (int col = 0; col < temperatures[row].length; col++) System.out.print(temperatures[row][col]+" "); System.out.println(); } Java provides an alternative for creating a multidimensional array in which you create each dimension separately. For example, to create a two-dimensional array via new in this manner, first create a one-dimensional row array (the outer array), and then create a one-dimensional column array (the inner array), as demonstrated here: // Create the row array. double[][] temperatures = new double[3][]; // Note the extra empty pair of brackets. // Create a column array for each row. for (int row = 0; row < temperatures.length; row++) temperatures[row] = new double[2]; // 2 columns per row This book was purchased by [email protected] This kind of an array is known as a ragged array because each row can have a different number of columns; the array is not rectangular, but is ragged. ■ Note When creating the row array, you must specify an extra pair of empty brackets as part of the expression following new. (For a three-dimensional array—a one-dimensional array of tables, where this array’s elements reference row arrays—you must specify two pairs of empty brackets as part of the expression following new.) You can combine new with Chapter 1’s array initialization syntax if desired. For example, Image[] imArray = new Image[] { new Image("image0.png"), new Image("image1.png") }; creates a pair of Image objects and a two-element Image array object initialized to the Image objects’ references, and assigns the array’s reference to imArray. When you create an array in this manner, you are not permitted to specify an integral expression between the square brackets. For example, the compiler reports an error when it encounters Image[] imArray = new Image[2] { new Image("image0.png"), new Image("image1.png") };. To correct this error, remove the 2 from between the square brackets. Encapsulating State and Behaviors Classes model real-world entities from a template perspective; for example, car and savings account. Objects represent specific entities; for example, John’s red Toyota Camry (a car instance) and Cuifen’s savings account with a balance of twenty thousand dollars (a savings account instance). Entities have attributes, such as color red, make Toyota, model Camry, and balance twenty thousand dollars. An entity’s collection of attributes is referred to as its state. Entities also have behaviors, such as open car door, drive car, display fuel consumption, deposit, withdraw, and show account balance. A class and its objects model an entity by combining state with behaviors into a single unit—the class abstracts state whereas its objects provide concrete state values. This bringing together of state and behaviors is known as encapsulation. Unlike structured programming, where the developer focuses on modeling behaviors through structured code, and modeling state through data structures that store data items for the structured code to manipulate, the developer working with classes and objects focuses on 59 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS templating entities by declaring classes that encapsulate state and behaviors, instantiating objects with specific state values from these classes to represent specific entities, and interacting with objects through their behaviors. This section first introduces you to Java’s language features for representing state, and then introduces you to its language features for representing behaviors. Because some state and behaviors support the class’s internal architecture, and should not be visible to those wanting to use the class, this section concludes by presenting the important concept of information hiding. Representing State via Fields Java lets you represent state via fields, which are variables declared within a class’s body. Entity attributes are described via instance fields. Because Java also supports state that’s associated with a class and not with an object, Java provides class fields to describe this class state. You first learn how to declare and access instance fields and then learn how to declare and access class fields. After discovering how to declare read-only instance and class fields, you review the rules for accessing fields from different contexts. Declaring and Accessing Instance Fields You can declare an instance field by minimally specifying a type name, followed by an identifier that names the field, followed by a semicolon character (;). Listing 2-3 presents a Car class with three instance field declarations. Listing 2-3. Declaring a Car class with make, model, and numDoors instance fields class Car { String make; String model; int numDoors; } Listing 2-3 declares two String instance fields named make and model. It also declares an int instance field named numDoors. By convention, a field’s name begins with a lowercase letter, and the first letter of each subsequent word in a multiword field name is capitalized. When an object is created, instance fields are initialized to default zero values, which you interpret at the source code level as literal value false, '\u0000', 0, 0L, 0.0, 0.0F, or null (depending on element type). For example, if you were to execute Car car = new Car();, make and model would be initialized to null and numDoors would be initialized to 0. You can assign values to or read values from an object’s instance fields by using the member access operator (.); the left operand specifies the object’s reference and the right operand specifies the instance field to be accessed. Listing 2-4 uses this operator to initialize a Car object’s make, model, and numDoors instance fields. Listing 2-4. Initializing a Car object’s instance fields class Car { String make; String model; 60 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS int numDoors; public static void main(String[] args) { Car car = new Car(); car.make = "Toyota"; car.model = "Camry"; car.numDoors = 4; } } Listing 2-4 presents a main() method that instantiates Car. The car instance’s make instance field is assigned the "Toyota" string, its model instance field is assigned the "Camry" string, and its numDoors instance field is assigned integer literal 4. (A string’s double quotes delimit a string’s sequence of characters but are not part of the string.) You can explicitly initialize an instance field when declaring that field to provide a nonzero default value, which overrides the default zero value. Listing 2-5 demonstrates this point. Listing 2-5. Initializing Car’s numDoors instance field to a default nonzero value class Car { String make; String model; int numDoors = 4; Car() { } public static void main(String[] args) { Car johnDoeCar = new Car(); johnDoeCar.make = "Chevrolet"; johnDoeCar.model = "Volt"; } } Listing 2-5 explicitly initializes numDoors to 4 because the developer has assumed that most cars being modeled by this class have four doors. When Car is initialized via the Car() constructor, the developer only needs to initialize the make and model instance fields for those cars that have four doors. It is usually not a good idea to directly initialize an object’s instance fields, and you will learn why when I discuss information hiding (later in this chapter). Instead, you should perform this initialization in the class’s constructor(s)—see Listing 2-6. Listing 2-6. Initializing Car’s instance fields via constructors class Car { String make; String model; int numDoors; Car(String make, String model) { this(make, model, 4); 61 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS } Car(String make, String model, int nDoors) { this.make = make; this.model = model; numDoors = nDoors; } public static void main(String[] args) { Car myCar = new Car("Toyota", "Camry"); Car yourCar = new Car("Mazda", "RX-8", 2); } } Listing 2-6’s Car class declares Car(String make, String model) and Car(String make, String model, int nDoors) constructors. The first constructor lets you specify the make and model, whereas the second constructor lets you specify values for the three instance fields. The first constructor executes this(make, model, 4); to pass the values of its make and model parameters, along with a default value of 4 to the second constructor. Doing so demonstrates an alternative to explicitly initializing an instance field, and is preferable from a code maintenance perspective. The Car(String make, String model, int numDoors) constructor demonstrates another use for keyword this. Specifically, it demonstrates a scenario where constructor parameters have the same names as the class’s instance fields. Prefixing a variable name with “this.” causes the Java compiler to create bytecode that accesses the instance field. For example, this.make = make; assigns the make parameter’s String object reference to this (the current) Car object’s make instance field. If make = make; was specified instead, it would accomplish nothing by assigning make’s value to itself; a Java compiler might not generate code to perform the unnecessary assignment. In contrast, “this.” isn’t necessary for the numDoors = nDoors; assignment, which initializes the numDoors field from the nDoors parameter value. Declaring and Accessing Class Fields In many situations, instance fields are all that you need. However, you might encounter a situation where you need a single copy of a field no matter how many objects are created. For example, suppose you want to track the number of Car objects that have been created, and introduce a counter instance field (initialized to 0) into this class. You also place code in the class’s constructor that increases counter’s value by 1 when an object is created. However, because each object has its own copy of the counter instance field, this field’s value never advances past 1. Listing 2-7 solves this problem by declaring counter to be a class field, by prefixing the field declaration with the static keyword. Listing 2-7. Adding a counter class field to Car class Car { String make; String model; int numDoors; static int counter; 62 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Car(String make, String model) { this(make, model, 4); } Car(String make, String model, int numDoors) { this.make = make; this.model = model; this.numDoors = numDoors; counter++; } public static void main(String[] args) { Car myCar = new Car("Toyota", "Camry"); Car yourCar = new Car("Mazda", "RX-8", 2); System.out.println(Car.counter); } } Listing 2-7’s static prefix implies that there is only one copy of the counter field, not one copy per object. When a class is loaded into memory, class fields are initialized to default zero values. For example, counter is initialized to 0. (As with instance fields, you can alternatively assign a value to a class field in its declaration.) Each time an object is created, counter will increase by 1 thanks to the counter++ expression in the Car(String make, String model, int numDoors) constructor. Unlike instance fields, class fields are normally accessed directly via the member access operator. Although you could access a class field via an object reference (as in myCar.counter), it is conventional to access a class field by using the class’s name, as in Car.counter. (It is also easier to tell that the code is accessing a class field.) ■ Note Because the main() method is a member of Listing 2-7’s Car class, you could access counter directly, as in System.out.println(counter);. To access counter in the context of another class’s main() method, however, you would have to specify Car.counter. If you run Listing 2-7, you will notice that it outputs 2, because two Car objects have been created. Declaring Read-Only Instance and Class Fields The previously declared fields can be written to as well as read from. However, you might want to declare a field that is read-only; for example, a field that names a constant value such as pi (3.14159…). Java lets you accomplish this task by providing reserved word final. Each object receives its own copy of a read-only instance field. This field must be initialized, as part of the field’s declaration or in the class’s constructor. If initialized in the constructor, the read-only instance field is known as a blank final because it does not have a value until one is assigned to it in the constructor. Because a constructor can potentially assign a different value to each object’s blank final, these read-only variables are not truly constants. 63 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS If you want a true constant, which is a single read-only value that is available to all objects, you need to create a read-only class field. You can accomplish this task by including the reserved word static with final in that field’s declaration. Listing 2-8 shows you how to declare a read-only class field. Listing 2-8. Declaring a true constant in the Employee class class Employee { final static int RETIREMENT_AGE = 65; } Listing 2-8’s RETIREMENT_AGE declaration is an example of a compile-time constant. Because there is only one copy of its value (thanks to the static keyword), and because this value will never change (thanks to the final keyword), the compiler is free to optimize the compiled code by inserting the constant value into all calculations where it is used. The code runs faster because it doesn’t have to access a read-only class field. Reviewing Field-Access Rules The previous examples of field access may seem confusing because you can sometimes specify the field’s name directly, whereas you need to prefix a field name with an object reference or a class name and the member access operator at other times. The following rules dispel this confusion by giving you guidance on how to access fields from the various contexts: • Specify the name of a class field as is from anywhere within the same class as the class field declaration. Example: counter • Specify the name of a class field’s class, followed by the member access operator, followed by the name of the class field from outside the class. Example: Car.counter • Specify the name of an instance field as is from any instance method, constructor, or instance initializer (discussed later) in the same class as the instance field declaration. Example: numDoors • Specify an object reference, followed by the member access operator, followed by the name of the instance field from any class method or class initializer (discussed later) within the same class as the instance field declaration, or from outside the class. Example: Car car = new Car(); car.numDoors = 2; Although the latter rule might seem to imply that you can access an instance field from a class context, this is not the case. Instead, you are accessing the field from an object context. The previous access rules are not exhaustive because there exist two more field-access scenarios to consider: declaring a local variable (or even a parameter) with the same name as an instance field or as a class field. In either scenario, the local variable/parameter is said to shadow (hide or mask) the field. If you find that you have declared a local variable or a parameter that shadows a field, you can rename the local variable/parameter, or you can use the member access operator with reserved word this (instance field) or class name (class field) to explicitly identify the field. For example, Listing 2-6’s Car(String make, String model, int nDoors) constructor demonstrated this latter solution by specifying statements such as this.make = make; to distinguish an instance field from a same-named parameter. 64 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Representing Behaviors via Methods Java lets you represent behaviors via methods, which are named blocks of code declared within a class’s body. Entity behaviors are described via instance methods. Because Java also supports behaviors that are associated with a class and not with an object, Java provides class methods to describe these class behaviors. You first learn how to declare and invoke instance methods, and then learn how to create instance method call chains. Next, you discover how to declare and invoke class methods, encounter additional details about passing arguments to methods, and explore Java’s return statement. After learning how to invoke methods recursively as an alternative to iteration, and how to overload methods, you review the rules for invoking methods from different contexts. Declaring and Invoking Instance Methods You can declare an instance method by minimally specifying a return type name, followed by an identifier that names the method, followed by a parameter list, followed by a brace-delimited body. Listing 2-9 presents a Car class with a printDetails() instance method. Listing 2-9. Declaring a printDetails() instance method in the Car class class Car { String make; String model; int numDoors; Car(String make, String model) { this(make, model, 4); } Car(String make, String model, int numDoors) { this.make = make; this.model = model; this.numDoors = numDoors; } void printDetails() { System.out.println("Make = "+make); System.out.println("Model = "+model); System.out.println("Number of doors = "+numDoors); System.out.println(); } public static void main(String[] args) { Car myCar = new Car("Toyota", "Camry"); myCar.printDetails(); Car yourCar = new Car("Mazda", "RX-8", 2); yourCar.printDetails(); } } 65 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-9 declares an instance method named printDetails(). By convention, a method’s name begins with a lowercase letter, and the first letter of each subsequent word in a multiword method name is capitalized. Methods are like constructors in that they have parameter lists. You pass arguments to these parameters when you call the method. Because printDetails() does not take arguments, its parameter list is empty. ■ Note A method’s name and the number, types, and order of its parameters are known as its signature. When a method is invoked, the code within its body is executed. In the case of printDetails(), this method’s body executes a sequence of System.out.println() method calls to output the values of its make, model, and numDoors instance fields. Unlike constructors, methods are declared to have return types. A return type identifies the kind of values returned by the method (e.g., int count() returns 32-bit integers). If a method does not return a value (and printDetails() does not), its return type is replaced with keyword void, as in void printDetails(). ■ Note Constructors don’t have return types because they cannot return values. If a constructor could return an arbitrary value, how would that value be returned? After all, the new operator returns a reference to an object, and how could new also return a constructor value? A method is invoked by using the member access operator; the left operand specifies the object’s reference and the right operand specifies the method to be called. For example, the myCar.printDetails() and yourCar.printDetails() expressions invoke the printDetails() instance method on the myCar and yourCar objects. Compile Listing 2-9 (javac Car.java) and run this application (java Car). You should observe the following output, whose different instance field values prove that printDetails() associates with an object: Make = Toyota Model = Camry Number of doors = 4 Make = Mazda Model = RX-8 Number of doors = 2 When an instance method is invoked, Java passes a hidden argument to the method (as the leftmost argument in a list of arguments). This argument is the reference to the object on which the method is invoked, and is represented at the source code level via reserved word this. You don’t need to prefix an instance field name with “this.” from within the method whenever you attempt to access an instance field name that isn’t also the name of a parameter because “this.” is assumed in this situation. 66 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS METHOD-CALL STACK Method invocations require a method-call stack (also known as a method-invocation stack) to keep track of the statements to which execution must return. Think of the method-call stack as a simulation of a pile of clean trays in a cafeteria—you pop (remove) the clean tray from the top of the pile and the dishwasher will push (insert) the next clean tray onto the top of the pile. When a method is invoked, the JVM pushes its arguments and the address of the first statement to execute following the invoked method onto the method-call stack. The JVM also allocates stack space for the method’s local variables. When the method returns, the JVM removes local variable space, pops the address and arguments off the stack, and transfers execution to the statement at this address. Chaining Together Instance Method Calls Two or more instance method calls can be chained together via the member access operator, which results in more compact code. To accomplish instance method call chaining, you need to re-architect your instance methods somewhat differently, as Listing 2-10 reveals. Listing 2-10. Implementing instance methods so that calls to these methods can be chained together class SavingsAccount { int balance; SavingsAccount deposit(int amount) { balance += amount; return this; } SavingsAccount printBalance() { System.out.println(balance); return this; } public static void main(String[] args) { new SavingsAccount().deposit(1000).printBalance(); } } Listing 2-10 shows that you must specify the class’s name as the instance method’s return type. Each of deposit() and printBalance() must specify SavingsAccount as the return type. Also, you must specify return this; (return current object’s reference) as the last statement—I discuss the return statement later. For example, new SavingsAccount().deposit(1000).printBalance(); creates a SavingsAccount object, uses the returned SavingsAccount reference to invoke SavingsAccount’s deposit() instance method, to add one thousand dollars to the savings account (I’m ignoring cents for convenience), and finally uses deposit()’s returned SavingsAccount reference (which is the same SavingsAccount instance) to invoke SavingsAccount’s printBalance() instance method to output the account balance. 67 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Declaring and Invoking Class Methods In many situations, instance methods are all that you need. However, you might encounter a situation where you need to describe a behavior that is independent of any object. For example, suppose you would like to introduce a utility class (a class consisting of static [class] methods) whose methods perform various kinds of conversions (such as converting from degrees Celsius to degrees Fahrenheit). You don’t want to create an object from this class in order to perform a conversion. Instead, you simply want to call a method and obtain its result. Listing 2-11 addresses this requirement by presenting a Conversions class with a pair of class methods. These methods can be called without having to create a Conversions object. Listing 2-11. A Conversions utility class with a pair of class methods class Conversions { static double c2f(double degrees) { return degrees*9.0/5.0+32; } static double f2c(double degrees) { return (degrees-32)*5.0/9.0; } } Listing 2-11’s Conversions class declares c2f() and f2c() methods for converting from degrees Celsius to degrees Fahrenheit and vice versa, and returning the results of these conversions. Each method header (method signature and other information) is prefixed with keyword static to turn the method into a class method. To execute a class method, you typically prefix its name with the class name. For example, you can execute Conversions.c2f(100.0); to find out the Fahrenheit equivalent of 100 degrees Celsius, and Conversions.f2c(98.6); to discover the Celsius equivalent of the normal body temperature. You don’t need to instantiate Conversions and then call these methods via that instance, although you could do so (but that isn’t good form). ■ Note Every application has at least one class method. Specifically, an application must specify public static void main(String[] args) to serve as the application’s entry point. The static reserved word makes this method a class method. (I will explain reserved word public later in this chapter.) Because class methods are not called with a hidden argument that refers to the current object, c2f(), f2c(), and main() cannot access an object’s instance fields or call its instance methods. These class methods can only access class fields and call class methods. 68 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Passing Arguments to Methods A method call includes a list of (zero or more) arguments being passed to the method. Java passes arguments to methods via a style of argument passing called pass-by-value, which the following example demonstrates: This book was purchased by [email protected] Employee emp = new Employee("John "); int recommendedAnnualSalaryIncrease = 1000; printReport(emp, recommendAnnualSalaryIncrease); printReport(new Employee("Cuifen"), 1500); Pass-by-value passes the value of a variable (the reference value stored in emp or the 1000 value stored in recommendedAnnualSalaryIncrease, for example) or the value of some other expression (such as new Employee("Cuifen") or 1500) to the method. Because of pass-by-value, you cannot assign a different Employee object’s reference to emp from inside printReport() via the printReport() parameter for this argument. After all, you have only passed a copy of emp’s value to the method. Many methods (and constructors) require you to pass a fixed number of arguments when they are called. However, Java also can pass a variable number of arguments—such methods/constructors are often referred to as varargs methods/constructors. To declare a method (or constructor) that takes a variable number of arguments, specify three consecutive periods after the type name of the method’s/constructor’s rightmost parameter. The following example presents a sum() method that accepts a variable number of arguments: double sum(double... values) { int total = 0; for (int i = 0; i < values.length; i++) total += values[i]; return total; } sum()’s implementation totals the number of arguments passed to this method; for example, sum(10.0, 20.0) or sum(30.0, 40.0, 50.0). (Behind the scenes, these arguments are stored in a onedimensional array, as evidenced by values.length and values[i].) After these values have been totaled, this total is returned via the return statement. Returning from a Method via the Return Statement The execution of statements within a method that does not return a value (its return type is set to void) flows from the first statement to the last statement. However, Java’s return statement lets a method (or a constructor) exit before reaching the last statement. As Listing 2-12 shows, this form of the return statement consists of reserved word return followed by a semicolon. 69 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-12. Using the return statement to return prematurely from a method class Employee { String name; Employee(String name) { setName(name); } void setName(String name) { if (name == null) { System.out.println("name cannot be null"); return; } else this.name = name; } public static void main(String[] args) { Employee john = new Employee(null); } } Listing 2-12’s Employee(String name) constructor invokes the setName() instance method to initialize the name instance field. Providing a separate method for this purpose is a good idea because it lets you initialize the instance field at construction time and also at a later time. (Perhaps the employee changes his or her name.) ■ Note When you invoke a class’s instance or class method from a constructor or method within the same class, you specify only the method’s name. You don’t prefix the method invocation with the member access operator and an object reference or class name. setName() uses an if statement to detect an attempt to assign a null reference to the name field. When such an attempt is detected, it outputs the “name cannot be null” error message and returns prematurely from the method so that the null value cannot be assigned (and replace a previously assigned name). ■ Caution When using the return statement, you might run into a situation where the compiler reports an “unreachable code” error message. It does so when it detects code that will never be executed and occupies memory unnecessarily. One area where you might encounter this problem is the switch statement. For example, 70 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS suppose you specify case "-v": printUsageInstructions(); return; break; as part of this statement. The compiler reports an error when it detects the break statement following the return statement because the break statement is unreachable; it never can be executed. The previous form of the return statement is not legal in a method that returns a value. For such methods, Java provides an alternate version of return that lets the method return a value (whose type must match the method’s return type). The following example demonstrates this version: double divide(double dividend, double divisor) { if (divisor == 0.0) { System.out.println("cannot divide by zero"); return 0.0; } return dividend/divisor; } divide() uses an if statement to detect an attempt to divide its first argument by 0.0, and outputs an error message when this attempt is detected. Furthermore, it returns 0.0 to signify this attempt. If there is no problem, the division is performed and the result is returned. ■ Caution You cannot use this form of the return statement in a constructor because constructors do not have return types. Invoking Methods Recursively A method normally executes statements that may include calls to other methods, such as printDetails() invoking System.out.println(). However, it is occasionally convenient to have a method call itself. This scenario is known as recursion. For example, suppose you need to write a method that returns a factorial (the product of all the positive integers up to and including a specific integer). For example, 3! (the ! is the mathematical symbol for factorial) equals 3×2×1 or 6. Your first approach to writing this method might consist of the code presented in the following example: int factorial(int n) { int product = 1; for (int i = 2; i <= n; i++) product *= i; return product; } 71 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Although this code accomplishes its task (via iteration), factorial() could also be written according to the following example’s recursive style. int factorial(int n) { if (n == 1) return 1; // base problem else return n*factorial(n-1); } The recursive approach takes advantage of being able to express a problem in simpler terms of itself. According to this example, the simplest problem, which is also known as the base problem, is 1! (1). When an argument greater than 1 is passed to factorial(), this method breaks the problem into a simpler problem by calling itself with the next smaller argument value. Eventually, the base problem will be reached. For example, calling factorial(4) results in the following stack of expressions: 4*factorial(3) 3*factorial(2) 2*factorial(1) This last expression is at the top of the stack. When factorial(1) returns 1, these expressions are evaluated as the stack begins to unwind: • 2*factorial(1) now becomes 2*1 (2) • 3*factorial(2) now becomes 3*2 (6) • 4*factorial(3) now becomes 4*6 (24) Recursion provides an elegant way to express many problems. Additional examples include searching tree-based data structures for specific values and, in a hierarchical file system, finding and outputting the names of all files that contain specific text. ■ Caution Recursion consumes stack space, so make sure that your recursion eventually ends in a base problem; otherwise, you will run out of stack space and your application will be forced to terminate. Overloading Methods Java lets you introduce methods with the same name but different parameter lists into the same class. This feature is known as method overloading. When the compiler encounters a method invocation expression, it compares the called method’s arguments list with each overloaded method’s parameter list as it looks for the correct method to invoke. Two same-named methods are overloaded when their parameter lists differ in number or order of parameters. For example, Java’s String class provides overloaded public int indexOf(int ch) and public int indexOf(int ch, int fromIndex) methods. These methods differ in parameter counts. (I explore String in Chapter 4.) 72 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Two same-named methods are overloaded when at least one parameter differs in type. For example, Java’s java.lang.Math class provides overloaded public static double abs(double a) and public static int abs(int a) methods. One method’s parameter is a double; the other method’s parameter is an int. (I explore Math in Chapter 4.) You cannot overload a method by changing only the return type. For example, double sum(double... values) and int sum(double... values) are not overloaded. These methods are not overloaded because the compiler does not have enough information to choose which method to call when it encounters sum(1.0, 2.0) in source code. Reviewing Method-Invocation Rules The previous examples of method invocation may seem confusing because you can sometimes specify the method’s name directly, whereas you need to prefix a method name with an object reference or a class name and the member access operator at other times. The following rules dispel this confusion by giving you guidance on how to invoke methods from the various contexts: • Specify the name of a class method as is from anywhere within the same class as the class method. Example: c2f(37.0); • Specify the name of the class method’s class, followed by the member access operator, followed by the name of the class method from outside the class. Example: Conversions.c2f(37.0); (You can also invoke a class method via an object instance, but that is considered bad form because it hides from casual observation the fact that a class method is being invoked.) • Specify the name of an instance method as is from any instance method, constructor, or instance initializer in the same class as the instance method. Example: setName(name); • Specify an object reference, followed by the member access operator, followed by the name of the instance method from any class method or class initializer within the same class as the instance method, or from outside the class. Example: Car car = new Car("Toyota", "Camry"); car.printDetails(); Although the latter rule might seem to imply that you can call an instance method from a class context, this is not the case. Instead, you call the method from an object context. Also, don’t forget to make sure that the number of arguments passed to a method, along with the order in which these arguments are passed, and the types of these arguments agree with their parameter counterparts in the method being invoked. ■ Note Field access and method call rules are combined in expression System.out.println();, where the leftmost member access operator accesses the out class field (of type java.io.PrintStream) in the java.lang.System class, and where the rightmost member access operator calls this field’s println() method. You’ll learn about PrintStream in Chapter 8 and System in Chapter 4. 73 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Hiding Information Every class X exposes an interface (a protocol consisting of constructors, methods, and [possibly] fields that are made available to objects created from other classes for use in creating and communicating with X’s objects). An interface serves as a one-way contract between a class and its clients, which are the external constructors, methods, and other (initialization-oriented) class entities (discussed later in this chapter) that communicate with the class’s instances by calling constructors and methods, and by accessing fields (typically public static final fields, or constants). The contract is such that the class promises to not change its interface, which would break clients that depend upon the interface. X also provides an implementation (the code within exposed methods along with optional helper methods and optional supporting fields that should not be exposed) that codifies the interface. Helper methods are methods that assist exposed methods and should not be exposed. When designing a class, your goal is to expose a useful interface while hiding details of that interface’s implementation. You hide the implementation to prevent developers from accidentally accessing parts of your class that do not belong to the class’s interface, so that you are free to change the implementation without breaking client code. Hiding the implementation is often referred to as information hiding. Furthermore, many developers consider implementation hiding to be part of encapsulation. Java supports implementation hiding by providing four levels of access control, where three of these levels are indicated via a reserved word. You can use the following access control levels to control access to fields, methods, and constructors, and two of these levels to control access to classes: • Public: A field, method, or constructor that is declared public is accessible from anywhere. Classes can be declared public as well. • Protected: A field, method, or constructor that is declared protected is accessible from all classes in the same package as the member’s class, as well as subclasses of that class regardless of package. (I will discuss packages in Chapter 3.) • Private: A field, method, or constructor that is declared private cannot be accessed from beyond the class in which it is declared. • Package-private: In the absence of an access-control reserved word, a field, method, or constructor is only accessible to classes within the same package as the member’s class. The same is true for non-public classes. The absence of public, protected, or private implies package-private. ■ Note A class that is declared public must be stored in a file with the same name. For example, a public Image class must be stored in Image.java. A source file can declare one public class only. You will often declare your class’s instance fields to be private and provide special public instance methods for setting and getting their values. By convention, methods that set field values have names starting with set and are known as setters. Similarly, methods that get field values have names with get (or is, for Boolean fields) prefixes and are known as getters. Listing 2-13 demonstrates this pattern in the context of an Employee class declaration. 74 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-13. Separation of interface from implementation public class Employee { private String name; public Employee(String name) { setName(name); } public void setName(String empName) { name = empName; // Assign the empName argument to the name field. } public String getName() { return name; } } Listing 2-13 presents an interface consisting of the public Employee class, its public constructor, and its public setter/getter methods. This class and these members can be accessed from anywhere. The implementation consists of the private name field and constructor/method code, which is only accessible within the Employee class. It might seem pointless to go to all this bother when you could simply omit private and access the name field directly. However, suppose you are told to introduce a new constructor that takes separate first and last name arguments and new methods that set/get the employee’s first and last names into this class. Furthermore, suppose that it has been determined that the first and last names will be accessed more often than the entire name. Listing 2-14 reveals these changes. Listing 2-14. Revising implementation without affecting existing interface public class Employee { private String firstName; private String lastName; public Employee(String name) { setName(name); } public Employee(String firstName, String lastName) { setName(firstName+" "+lastName); } public void setName(String name) { // Assume that the first and last names are separated by a // single space character. indexOf() locates a character in a // string; substring() returns a portion of a string. setFirstName(name.substring(0, name.indexOf(' '))); setLastName(name.substring(name.indexOf(' ')+1)); } public String getName() 75 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS { return getFirstName()+" "+getLastName(); } public void setFirstName(String empFirstName) { firstName = empFirstName; } public String getFirstName() { return firstName; } public void setLastName(String empLastName) { lastName = empLastName; } public String getLastName() { return lastName; } } Listing 2-14 reveals that the name field has been removed in favor of new firstName and lastName fields, which were added to improve performance. Because setFirstName() and setLastName() will be called more frequently than setName(), and because getFirstName() and getLastName() will be called more frequently than getName(), it is more performant (in each case) to have the first two methods set/get firstName’s and lastName’s values rather than merging either value into/extracting this value from name’s value. Listing 2-14 also reveals setName() calling setFirstName() and setLastName(), and getName() calling getFirstName() and getLastName(), rather than directly accessing the firstName and lastName fields. Although avoiding direct access to these fields is not necessary in this example, imagine another implementation change that adds more code to setFirstName(), setLastName(), getFirstName(), and getLastName(); not calling these methods will result in the new code not executing. Client code (code that instantiates and uses a class, such as Employee) will not break when Employee’s implementation changes from that shown in Listing 2-13 to that shown in Listing 2-14, because the original interface remains intact, although the interface has been extended. This lack of breakage results from hiding Listing 2-13’s implementation, especially the name field. ■ Note setName() invokes the String class’s indexOf() and substring() methods. You’ll learn about these and other String methods in Chapter 4. Java provides a little known information hiding-related language feature that lets one object (or class method/initializer) access another object’s private fields or invoke its private methods. Listing 215 provides a demonstration. 76 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-15. One object accessing another object’s private field class PrivateAccess { private int x; PrivateAccess(int x) { this.x = x; } boolean equalTo(PrivateAccess pa) { return pa.x == x; } public static void main(String[] args) { PrivateAccess pa1 = new PrivateAccess(10); PrivateAccess pa2 = new PrivateAccess(20); PrivateAccess pa3 = new PrivateAccess(10); System.out.println("pa1 equal to pa2: "+pa1.equalTo(pa2)); System.out.println("pa2 equal to pa3: "+pa2.equalTo(pa3)); System.out.println("pa1 equal to pa3: "+pa1.equalTo(pa3)); System.out.println(pa2.x); } } Listing 2-15’s PrivateAccess class declares a private int field named x. It also declares an equalTo() method that takes a PrivateAccess argument. The idea is to compare the argument object with the current object to determine if they are equal. The equality determination is made by using the == operator to compare the value of the argument object’s x instance field with the value of the current object’s x instance field, returning Boolean true when they are the same. What may seem baffling is that Java lets you specify pa.x to access the argument object’s private instance field. Also, main() is able to directly access x, via the pa2 object. I previously presented Java’s four access-control levels and presented the following statement regarding the private access-control level: “A field, method, or constructor that is declared private cannot be accessed from beyond the class in which it is declared.” When you carefully consider this statement and examine Listing 2-15, you will realize that x is not being accessed from beyond the PrivateAccess class in which it is declared. Therefore, the private access-control level is not being violated. The only code that can access this private instance field is code located within the PrivateAccess class. If you attempted to access x via a PrivateAccess object that was created in the context of another class, the compiler would report an error. Being able to directly access x from within PrivateAccess is a performance enhancement; it is faster to directly access this implementation detail than to call a method that returns its value. Compile PrivateAccess.java (javac PrivateAccess.java) and run the application (java PrivateAccess). You should observe the following output: pa1 equal to pa2: false pa2 equal to pa3: false pa1 equal to pa3: true 20 77 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Tip Get into the habit of developing useful interfaces while hiding implementations because it will save you much trouble when maintaining your classes. Initializing Classes and Objects Classes and objects need to be properly initialized before they are used. You’ve already learned that class fields are initialized to default zero values after a class loads, and can be subsequently initialized by assigning values to them in their declarations via class field initializers; for example, static int counter = 1;. Similarly, instance fields are initialized to default values when an object’s memory is allocated via new, and can be subsequently initialized by assigning values to them in their declarations via instance field initializers; for example, int numDoors = 4;. Another aspect of initialization that’s already been discussed is the constructor, which is used to initialize an object, typically by assigning values to various instance fields, but is also capable of executing arbitrary code, such as code that opens a file and reads the file’s contents. Java provides two additional initialization features: class initializers and instance initializers. After introducing you to these features, this section discusses the order in which all of Java’s initializers perform their work. Class Initializers Constructors perform initialization tasks for objects. Their counterpart from a class initialization perspective is the class initializer. A class initializer is a static-prefixed block that is introduced into a class body. It is used to initialize a loaded class via a sequence of statements. For example, I once used a class initializer to load a custom database driver class. Listing 2-16 shows the loading details. Listing 2-16. Loading a database driver via a class initializer class JDBCFilterDriver implements Driver { static private Driver d; static { // Attempt to load JDBC-ODBC Bridge Driver and register that // driver. try { Class c = Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); d = (Driver) c.newInstance(); DriverManager.registerDriver(new JDBCFilterDriver()); } catch (Exception e) { System.out.println(e); } } 78 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS //... } Listing 2-16’s JDBCFilterDriver class uses its class initializer to load and instantiate the class that describes Java’s JDBC-ODBC Bridge Driver, and to register a JDBCFilterDriver instance with Java’s database driver. Although this listing’s JDBC-oriented code is probably meaningless to you right now, the listing illustrates the usefulness of class initializers. (I discuss JDBC in Chapter 9.) A class can declare a mix of class initializers and class field initializers, as demonstrated in Listing 217. This book was purchased by [email protected] Listing 2-17. Mixing class initializers with class field initializers class C { static { System.out.println("class initializer 1"); } static int counter = 1; static { System.out.println("class initializer 2"); System.out.println("counter = "+counter); } } Listing 2-17 declares a class named C that specifies two class initializers and one class field initializer. When the Java compiler compiles into a classfile a class that declares at least one class initializer or class field initializer, it creates a special void <clinit>() class method that stores the bytecode equivalent of all class initializers and class field initializers in the order they occur (from top to bottom). ■ Note <clinit> is not a valid Java method name, but is a valid name from the runtime perspective. The angle brackets were chosen as part of the name to prevent a name conflict with any clinit() methods that you might declare in the class. For class C, <clinit>() would first contain the bytecode equivalent of System.out.println("class initializer 1");, it would next contain the bytecode equivalent of static int counter = 1;, and it would finally contain the bytecode equivalent of System.out.println("class initializer 2"); System.out.println("counter = "+counter);. When class C is loaded into memory, <clinit>() executes immediately and generates the following output: class initializer 1 class initializer 2 counter = 1 79 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Instance Initializers Not all classes can have constructors, as you will discover in Chapter 3 when I present anonymous classes. For these classes, Java supplies the instance initializer to take care of instance initialization tasks. An instance initializer is a block that is introduced into a class body, as opposed to being introduced as the body of a method or a constructor. The instance initializer is used to initialize an object via a sequence of statements, as demonstrated in Listing 2-18. Listing 2-18. Initializing a pair of arrays via an instance initializer class Graphics { double[] sines; double[] cosines; { sines = new double[360]; cosines = new double[sines.length]; for (int i = 0; i < sines.length; i++) { sines[i] = Math.sin(Math.toRadians(i)); cosines[i] = Math.cos(Math.toRadians(i)); } } } Listing 2-18’s Graphics class uses an instance initializer to create an object’s sines and cosines arrays, and to initialize these arrays’ elements to the sines and cosines of angles ranging from 0 through 359 degrees. It does so because it’s faster to read array elements than to repeatedly call Math.sin() and Math.cos() elsewhere; performance matters. (Chapter 4 introduces Math.sin() and Math.cos().) A class can declare a mix of instance initializers and instance field initializers, as shown in Listing 219. Listing 2-19. Mixing instance initializers with instance field initializers class C { { System.out.println("instance initializer 1"); } int counter = 1; { System.out.println("instance initializer 2"); System.out.println("counter = "+counter); } } Listing 2-19 declares a class named C that specifies two instance initializers and one instance field initializer. When the Java compiler compiles a class into a classfile, it creates a special void <init>() method representing the default noargument constructor when no constructor is explicitly declared; otherwise, it create an <init>() method for each encountered constructor. Furthermore, it stores in each constructor the bytecode equivalent of all instance initializers and instance field initializers in the order they occur (from top to bottom). 80 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Note <init> is not a valid Java method name, but is a valid name from the runtime perspective. The angle brackets were chosen as part of the name to prevent a name conflict with any init() methods that you might declare in the class. For class C, <init>() would first contain the bytecode equivalent of System.out.println("instance initializer 1");, it would next contain the bytecode equivalent of int counter = 1;, and it would finally contain the bytecode equivalent of System.out.println("instance initializer 2"); System.out.println("counter = "+counter);. When new C() executes, <init>() executes immediately and generates the following output: instance initializer 1 instance initializer 2 counter = 1 ■ Note You should rarely need to use the instance initializer, which is not commonly used in industry. Initialization Order A class’s body can contain a mixture of class field initializers, class initializers, instance field initializers, instance initializers, and constructors. (You should prefer constructors to instance field initializers, although I am guilty of not doing so consistently, and restrict your use of instance initializers to anonymous classes.) Furthermore, class fields and instance fields initialize to default values. Understanding the order in which all of this initialization occurs is necessary to preventing confusion, so check out Listing 2-20. Listing 2-20. A complete initialization demo class InitDemo { static double double1; double double2; static int int1; int int2; static String string1; String string2; static { System.out.println("[class] double1 = "+double1); System.out.println("[class] int1 = "+int1); System.out.println("[class] string1 = "+string1); System.out.println(); } { 81 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS System.out.println("[instance] double2 = "+double2); System.out.println("[instance] int2 = "+int2); System.out.println("[instance] string2 = "+string2); System.out.println(); } static { double1 = 1.0; int1 = 1000000000; string1 = "abc"; } { double2 = 1.0; int2 = 1000000000; string2 = "abc"; } InitDemo() { System.out.println("InitDemo() called"); System.out.println(); } static double double3 = 10.0; double double4 = 10.0; static { System.out.println("[class] double3 = "+double3); System.out.println(); } { System.out.println("[instance] double4 = "+double3); System.out.println(); } public static void main(String[] args) { System.out.println ("main() started"); System.out.println(); System.out.println("[class] double1 = "+double1); System.out.println("[class] double3 = "+double3); System.out.println("[class] int1 = "+int1); System.out.println("[class] string1 = "+string1); System.out.println(); for (int i = 0; i < 2; i++) { System.out.println("About to create InitDemo object"); System.out.println(); InitDemo id = new InitDemo(); System.out.println("id created"); System.out.println(); System.out.println("[instance] id.double2 = "+id.double2); System.out.println("[instance] id.double4 = "+id.double4); System.out.println("[instance] id.int2 = "+id.int2); System.out.println("[instance] id.string2 = "+id.string2); 82 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS System.out.println(); } } } Listing 2-20’s InitDemo class declares two class fields and two instance fields for the double precision floating-point primitive type, one class field and one instance field for the integer primitive type, and one class field and one instance field for the String reference type. It also introduces one explicitly initialized class field, one explicitly initialized instance field, three class initializers, three instance initializers, and one constructor. If you compile and run this code, you will observe the following output: [class] double1 = 0.0 [class] int1 = 0 [class] string1 = null [class] double3 = 10.0 main() started [class] [class] [class] [class] double1 = 1.0 double3 = 10.0 int1 = 1000000000 string1 = abc About to create InitDemo object [instance] double2 = 0.0 [instance] int2 = 0 [instance] string2 = null [instance] double4 = 10.0 InitDemo() called id created [instance] [instance] [instance] [instance] id.double2 = 1.0 id.double4 = 10.0 id.int2 = 1000000000 id.string2 = abc About to create InitDemo object [instance] double2 = 0.0 [instance] int2 = 0 [instance] string2 = null [instance] double4 = 10.0 InitDemo() called 83 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS id created [instance] [instance] [instance] [instance] id.double2 = 1.0 id.double4 = 10.0 id.int2 = 1000000000 id.string2 = abc As you study this output in conjunction with the aforementioned discussion of class initializers and instance initializers, you will discover some interesting facts about initialization: • Class fields initialize to default or explicit values just after a class is loaded. Immediately after a class loads, all class fields are zeroed to default values. Code within the <clinit>() method performs explicit initialization. • All class initialization occurs prior to the <clinit>() method returning. • Instance fields initialize to default or explicit values during object creation. When new allocates memory for an object, it zeroes all instance fields to default values. Code within an <init>() method performs explicit initialization. • All instance initialization occurs prior to the <init>() method returning. Additionally, because initialization occurs in a top-down manner, attempting to access the contents of a class field before that field is declared, or attempting to access the contents of an instance field before that field is declared causes the compiler to report an illegal forward reference. Inheriting State and Behaviors We tend to categorize stuff by saying things like “cars are vehicles” or “savings accounts are bank accounts.” By making these statements, we really are saying that cars inherit vehicular state (e.g., make and color) and behaviors (e.g., park and display mileage), and that savings accounts inherit bank account state (e.g., balance) and behaviors (e.g., deposit and withdraw). Car, vehicle, savings account, and bank account are examples of real-world entity categories, and inheritance is a hierarchical relationship between similar entity categories in which one category inherits state and behaviors from at least one other entity category. Inheriting from a single category is called single inheritance, and inheriting from at least two categories is called multiple inheritance. Java supports single inheritance and multiple inheritance to facilitate code reuse—why reinvent the wheel? Java supports single inheritance in a class context, in which a class inherits state and behaviors from another class through class extension. Because classes are involved, Java refers to this kind of inheritance as implementation inheritance. Java supports multiple inheritance only in an interface context, in which a class inherits behavior templates from one or more interfaces through interface implementation, or in which an interface inherits behavior templates from one or more interfaces through interface extension. Because interfaces are involved, Java refers to this kind of inheritance as interface inheritance. (I discuss interfaces later in this chapter.) This section introduces you to Java’s support for implementation inheritance by first focusing on class extension. It then introduces you to a special class that sits at the top of Java’s class hierarchy. After introducing you to composition, which is an alternative to implementation inheritance for reusing code, this section shows you how composition can be used to overcome problems with implementation inheritance. 84 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Extending Classes Java provides the reserved word extends for specifying a hierarchical relationship between two classes. For example, suppose you have a Vehicle class and want to introduce Car and Truck classes that extend Vehicle. Listing 2-21 uses extends to cement these relationships. Listing 2-21. Relating classes via extends class { // } class { // } class { // } Vehicle member declarations Car extends Vehicle member declarations Truck extends Vehicle Member declarations Listing 2-21 codifies relationships that are known as “is-a” relationships: a car or a truck is a kind of vehicle. In this relationship, Vehicle is known as the base class, parent class, or superclass; and each of Car and Truck is known as the derived class, child class, or subclass. ■ Caution You cannot extend a final class. For example, if you declared Vehicle as final class Vehicle, the compiler would report an error upon encountering class Car extends Vehicle or class Truck extends Vehicle. Developers declare their classes final when they do not want these classes to be extended (for security or other reasons). As well as being capable of providing its own member declarations, each of Car and Truck is capable of inheriting member declarations from its Vehicle superclass. As Listing 2-22 shows, non-private inherited members become accessible to members of the Car and Truck classes. Listing 2-22. Inheriting members class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; 85 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS } String getMake() { return make; } String getModel() { return model; } int getYear() { return year; } } class Car extends Vehicle { private int numWheels; Car(String make, String model, int year, int numWheels) { super(make, model, year); this.numWheels = numWheels; } public static void main(String[] args) { Car car = new Car("Toyota", "Camry", 2011, 4); System.out.println("Make = "+car.getMake()); System.out.println("Model = "+car.getModel()); System.out.println("Year = "+car.getYear()); System.out.println("Number of wheels = "+car.numWheels); System.out.println(); car = new Car("Aptera Motors", "Aptera 2e/2h", 2012, 3); System.out.println("Make = "+car.getMake()); System.out.println("Model = "+car.getModel()); System.out.println("Year = "+car.getYear()); System.out.println("Number of wheels = "+car.numWheels); } } class Truck extends Vehicle { private boolean isExtendedCab; Truck(String make, String model, int year, boolean isExtendedCab) { super(make, model, year); this.isExtendedCab = isExtendedCab; } public static void main(String[] args) { Truck truck = new Truck("Chevrolet", "Silverado", 2011, true); System.out.println("Make = "+truck.getMake()); System.out.println("Model = "+truck.getModel()); System.out.println("Year = "+truck.getYear()); System.out.println("Extended cab = "+truck.isExtendedCab); 86 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS } } Listing 2-22’s Vehicle class declares private fields that store a vehicle’s make, model, and year; a constructor that initializes these fields to passed arguments; and getter methods that retrieve these fields’ values. The Car subclass provides a private numWheels field, a constructor that initializes a Car object’s Vehicle and Car layers, and a main() class method for testing this class. Similarly, the Truck subclass provides a private isExtendedCab field, a constructor that initializes a Truck object’s Vehicle and Truck layers, and a main() class method for testing this class. Car’s and Truck’s constructors use reserved word super to call Vehicle’s constructor with Vehicleoriented arguments, and then initialize Car’s numWheels and Truck’s isExtendedCab instance fields, respectively. The super() call is analogous to specifying this() to call another constructor in the same class, but invokes a superclass constructor instead. ■ Caution The super() call can only appear in a constructor. Furthermore, it must be the first code that is specified in the constructor. If super() is not specified, and if the superclass does not have a noargument constructor, the compiler will report an error because the subclass constructor must call a noargument superclass constructor when super() is not present. Car’s main() method creates two Car objects, initializing each object to a specific make, model, year, and number of wheels. Four System.out.println() method calls subsequently output each object’s information. Similarly, Truck’s main() method creates a single Truck object, and also initializes this object to a specific make, model, year, and flag (Boolean true/false value) indicating that the truck is an extended cab. The first three System.out.println() method calls retrieve their pieces of information by calling a Car or Truck instance’s inherited getMake(), getModel(), and getYear() methods. The final System.out.println() method call directly accesses the instance’s numWheels or isExtendedCab instance field. Although it’s generally not a good idea to access an instance field directly (because it violates information hiding), each of the Car and Truck class’s main() methods, which provides this access, is present only to test these classes and would not exist in a real application that uses these classes. Assuming that Listing 2-22 is stored in a file named Vehicle.java, execute javac Vehicle.java to compile this source code into Vehicle.class, Car.class, and Truck.class classfiles. Then execute java Car to test the Car class. This execution results in the following output: Make = Toyota Model = Camry Year = 2011 Number of wheels = 4 Make = Aptera Motors Model = Aptera 2e/2h Year = 2012 Number of wheels = 3 Continuing, execute java Truck to test the Truck class. This execution results in the following output: 87 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Make = Chevrolet Model = Silverado Year = 2011 Extended cab = true ■ Note A class whose instances cannot be modified is known as an immutable class. Vehicle is an example. If Car’s and Truck’s main() methods, which can directly read/write numWheels or isExtendedCab, were not present, Car and Truck would also be examples of immutable classes. Also, a class cannot inherit constructors, nor can it inherit private fields and methods. For example, Car does not inherit Vehicle’s constructor, nor does it inherit Vehicle’s private make, model, and year fields. A subclass can override (replace) an inherited method so that the subclass’s version of the method is called instead. Listing 2-23 shows you that the overriding method must specify the same name, parameter list, and return type as the method being overridden. Listing 2-23. Overriding a method class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; } void describe() { System.out.println(year+" "+make+" "+model); } } class Car extends Vehicle { private int numWheels; Car(String make, String model, int year, int numWheels) { super(make, model, year); } void describe() { System.out.print("This car is a "); // Print without newline – see Chapter 1. super.describe(); } public static void main(String[] args) 88 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS { Car car = new Car("Ford", "Fiesta", 2009, 4); car.describe(); } } Listing 2-23’s Car class declares a describe() method that overrides Vehicle’s describe() method to output a car-oriented description. This method uses reserved word super to call Vehicle’s describe() method via super.describe();. This book was purchased by [email protected] ■ Note Call a superclass method from the overriding subclass method by prefixing the method’s name with reserved word super and the member access operator. If you don’t do this, you end up recursively calling the subclass’s overriding method. Use super and the member access operator to access non-private superclass fields from subclasses that mask these fields by declaring same-named fields. If you were to compile Listing 2-23 (javac Vehicle.java) and run the Car application (java Car), you would discover that Car’s overriding describe() method executes instead of Vehicle’s overridden describe() method, and outputs This car is a 2009 Ford Fiesta. ■ Caution You cannot override a final method. For example, if Vehicle’s describe() method was declared as final void describe(), the compiler would report an error upon encountering an attempt to override this method in the Car class. Developers declare their methods final when they do not want these methods to be overridden (for security or other reasons). Also, you cannot make an overriding method less accessible than the method it overrides. For example, if Car’s describe() method was declared as private void describe(), the compiler would report an error because private access is less accessible than the default package access. However, describe() could be made more accessible by declaring it public, as in public void describe(). Suppose you were to replace Listing 2-23’s describe() method with the method shown here: void describe(String owner) { System.out.print("This car, which is owned by "+owner+", is a "); super.describe(); } The modified Car class now has two describe() methods, the preceding explicitly declared method and the method inherited from Vehicle. The void describe(String owner) method does not override Vehicle’s describe() method. Instead, it overloads this method. The Java compiler helps you detect an attempt to overload instead of override a method at compile time by letting you prefix a subclass’s method header with the @Override annotation, as shown below—I discuss annotations in Chapter 3: 89 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS @Override void describe() { System.out.print("This car is a "); super.describe(); } Specifying @Override tells the compiler that the method overrides another method. If you overload the method instead, the compiler reports an error. Without this annotation, the compiler would not report an error because method overloading is a valid feature. ■ Tip Get into the habit of prefixing overriding methods with the @Override annotation. This habit will help you detect overloading mistakes much sooner. I previously presented the initialization order of classes and objects, where you learned that class members are always initialized first, and in a top-down order (the same order applies to instance members). Implementation inheritance adds a couple more details: • A superclass’s class initializers always execute before a subclass’s class initializers. • A subclass’s constructor always calls the superclass constructor to initialize an object’s superclass layer before initializing the subclass layer. Java’s support for implementation inheritance only permits you to extend a single class. You cannot extend multiple classes because doing so can lead to problems. For example, suppose Java supported multiple implementation inheritance, and you decided to model a flying horse (from Greek mythology) via the class structure shown in Listing 2-24. Listing 2-24. A fictional demonstration of multiple implementation inheritance class Horse { void describe() { // Code that outputs a description of a horse's appearance and behaviors. } } class Bird { void describe() { // Code that outputs a description of a bird's appearance and behaviors. } } class FlyingHorse extends Horse, Bird { public static void main(String[] args) { 90 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS FlyingHorse pegasus = new FlyingHorse(); pegasus.describe(); } } This class structure reveals an ambiguity resulting from each of Horse and Bird declaring a describe() method. Which of these methods does FlyingHorse inherit? A related ambiguity arises from same-named fields, possibly of different types. Which field is inherited? The Ultimate Superclass A class that does not explicitly extend another class implicitly extends Java’s Object class (located in the java.lang package—I will discuss packages in the next chapter). For example, Listing 2-1’s Image class extends Object, whereas Listing 2-21’s Car and Truck classes extend Vehicle, which extends Object. Object is Java’s ultimate superclass because it serves as the ancestor of every other class, but does not itself extend any other class. Object provides a common set of methods that other classes inherit. Table 2-1 describes these methods. Table 2-1. Object’s Methods Method Description Object clone() Create and return a copy of the current object. boolean equals(Object obj) Determine whether the current object is equal to the object identified by obj. void finalize() Finalize the current object. Class<?> getClass() Return the current object’s Class object. int hashCode() Return the current object’s hash code. void notify() Wake up one of the threads that are waiting on the current object’s monitor. void notifyAll() Wake up all threads that are waiting on the current object’s monitor. String toString() Return a string representation of the current object. void wait() Cause the current thread to wait on the current object’s monitor until it is woken up via notify() or notifyAll(). void wait(long timeout) Cause the current thread to wait on the current object’s monitor until it is woken up via notify() or notifyAll(), or until the specified timeout value (in 91 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS milliseconds) has elapsed, whichever comes first. void wait(long timeout, int nanos) Cause the current thread to wait on the current object’s monitor until it is woken up via notify() or notifyAll(), or until the specified timeout value (in milliseconds) plus nanos value (in nanoseconds) has elapsed, whichever comes first. I will discuss the clone(), equals(), finalize(), hashCode(), and toString() methods shortly, but defer a discussion of getClass(), notify(), notifyAll(), and the wait() methods to Chapter 4. ■ Note Chapter 6 introduces you to the java.util.Objects class, which provides several null-safe or nulltolerant class methods for comparing two objects, computing the hash code of an object, requiring that a reference not be null, and returning a string representation of an object. Cloning The clone() method clones (duplicates) an object without calling a constructor. It copies each primitive or reference field’s value to its counterpart in the clone, a task known as shallow copying or shallow cloning. Listing 2-25 demonstrates this behavior. Listing 2-25. Shallowly cloning an Employee object class Employee implements Cloneable { String name; int age; Employee(String name, int age) { this.name = name; this.age = age; } public static void main(String[] args) throws CloneNotSupportedException { Employee e1 = new Employee("John Doe", 46); Employee e2 = (Employee) e1.clone(); System.out.println(e1 == e2); // Output: false System.out.println(e1.name == e2.name); // Output: true } } Listing 2-25 declares an Employee class with name and age instance fields, and a constructor for initializing these fields. The main() method uses this constructor to initialize a new Employee object’s copies of these fields to John Doe and 46. 92 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Note A class must implement the java.lang.Cloneable interface or its instances cannot be shallowly cloned via Object’s clone() method—this method performs a runtime check to see if the class implements Cloneable. (I will discuss interfaces later in this chapter.) If a class does not implement Cloneable, clone() throws java.lang.CloneNotSupportedException. (Because CloneNotSupportedException is a checked exception, it is necessary for Listing 2-25 to satisfy the compiler by appending throws CloneNotSupportedException to the main() method’s header. I will discuss exceptions in the next chapter.) String is an example of a class that does not implement Cloneable; hence, String objects cannot be shallowly cloned. After assigning the Employee object’s reference to local variable e1, main() calls the clone() method on this variable to duplicate the object, and then assigns the resulting reference to variable e2. The (Employee) cast is needed because clone() returns Object. To prove that the objects whose references were assigned to e1 and e2 are different, main() next compares these references via == and outputs the Boolean result, which happens to be false. To prove that the Employee object was shallowly cloned, main() next compares the references in both Employee objects’ name fields via == and outputs the Boolean result, which happens to be true. ■ Note Object’s clone() method was originally specified as a public method, which meant that any object could be cloned from anywhere. For security reasons, this access was later changed to protected, which means that only code within the same package as the class whose clone() method is to be called, or code within a subclass of this class (regardless of package) can call clone(). Shallow cloning is not always desirable because the original object and its clone refer to the same object via their equivalent reference fields. For example, each of Listing 2-25’s two Employee objects refers to the same String object via its name field. Although not a problem for String, whose instances are immutable, changing a mutable object via the clone’s reference field causes the original (noncloned) object to see the same change via its reference field. For example, suppose you add a reference field named hireDate to Employee. This field is of type Date with year, month, and day instance fields. Because Date is intended to be mutable, you can change the contents of these fields in the Date instance assigned to hireDate. Now suppose you plan to change the clone’s date, but want to preserve the original Employee object’s date. You cannot do this with shallow cloning because the change is also visible to the original Employee object. To solve this problem, you must modify the cloning operation so that it assigns a new Date reference to the Employee clone’s hireDate field. This task, which is known as deep copying or deep cloning, is demonstrated in Listing 2-26. Listing 2-26. Deeply cloning an Employee object class Date { int year, month, day; 93 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } } class Employee implements Cloneable { String name; int age; Date hireDate; Employee(String name, int age, Date hireDate) { this.name = name; this.age = age; this.hireDate = hireDate; } @Override protected Object clone() throws CloneNotSupportedException { Employee emp = (Employee) super.clone(); if (hireDate != null) // no point cloning a null object (one that does not exist) emp.hireDate = new Date(hireDate.year, hireDate.month, hireDate.day); return emp; } public static void main(String[] args) throws CloneNotSupportedException { Employee e1 = new Employee("John Doe", 46, new Date(2000, 1, 20)); Employee e2 = (Employee) e1.clone(); System.out.println(e1 == e2); // Output: false System.out.println(e1.name == e2.name); // Output: true System.out.println(e1.hireDate == e2.hireDate); // Output: false System.out.println(e2.hireDate.year+" "+e2.hireDate.month+" "+ e2.hireDate.day); // Output: 2000 1 20 } } Listing 2-26 declares Date and Employee classes. The Date class declares year, month, and day fields and a constructor. Employee overrides the clone() method to deeply clone the hireDate field. This method first calls Object’s clone() method to shallowly clone the current Employee object’s instance fields, and then stores the new object’s reference in emp. It next assigns a new Date object’s reference to emp’s hireDate field; this object’s fields are initialized to the same values as those in the original Employee object’s hireDate instance. At this point, you have an Employee clone with shallowly cloned name and age fields, and a deeply cloned hireDate field. The clone() method finishes by returning this Employee clone. 94 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Note If you are not calling Object’s clone() method from an overriding clone() method (because you prefer to deeply clone reference fields and do your own shallow copying of nonreference fields), it isn’t necessary for the class containing the overriding clone() method to implement Cloneable, but it should implement this interface for consistency. String does not override clone(), so String objects cannot be deeply cloned. Equality The == and != operators compare two primitive values (such as integers) for equality (==) or inequality (!=). These operators also compare two references to see whether they refer to the same object or not. This latter comparison is known as an identity check. You cannot use == and != to determine whether two objects are logically the same (or not). For example, two Truck objects with the same field values are logically equivalent. However, == reports them as unequal because of their different references. ■ Note Because == and != perform the fastest possible comparisons, and because string comparisons need to be performed quickly (especially when sorting a huge number of strings), the String class contains special support that allows literal strings and string-valued constant expressions to be compared via == and !=. (I will discuss this support when I present String in Chapter 4.) The following statements demonstrate these comparisons: System.out.println("abc" == "abc"); // Output: true System.out.println("abc" == "a"+"bc"); // Output: true System.out.println("abc" == "Abc"); // Output: false System.out.println("abc" != "def"); // Output: true System.out.println("abc" == new String("abc")); // Output: false Recognizing the need to support logical equality in addition to reference equality, Java provides an equals() method in the Object class. Because this method defaults to comparing references, you need to override equals() to compare object contents. Before overriding equals(), make sure that this is necessary. For example, Java’s java.lang.StringBuffer class (discussed in Chapter 4) does not override equals(). Perhaps this class’s designers did not think it necessary to determine if two StringBuffer objects are logically equivalent. You cannot override equals() with arbitrary code. Doing so will probably prove disastrous to your applications. Instead, you need to adhere to the contract that is specified in the Java documentation for this method, and which I present next. The equals() method implements an equivalence relation on nonnull object references: • It is reflexive: For any nonnull reference value x, x.equals(x) returns true. 95 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS • It is symmetric: For any nonnull reference values x and y, x.equals(y) returns true if and only if y.equals(x) returns true. • It is transitive: For any nonnull reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) returns true. • It is consistent: For any nonnull reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals() comparisons on the objects is modified. • For any nonnull reference value x, x.equals(null) returns false. Although this contract probably looks somewhat intimidating, it is not that difficult to satisfy. For proof, take a look at the implementation of the equals() method in Listing 2-27’s Point class. Listing 2-27. Logically comparing Point objects class Point { private int x, y; Point(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return p.x == x && p.y == y; } public static void main(String[] args) { Point p1 = new Point(10, 20); Point p2 = new Point(20, 30); Point p3 = new Point(10, 20); // Test reflexivity System.out.println(p1.equals(p1)); // Output: true // Test symmetry System.out.println(p1.equals(p2)); // Output: false System.out.println(p2.equals(p1)); // Output: false // Test transitivity System.out.println(p2.equals(p3)); // Output: false System.out.println(p1.equals(p3)); // Output: true // Test nullability System.out.println(p1.equals(null)); // Output: false // Extra test to further prove the instanceof operator's usefulness. System.out.println(p1.equals("abc")); // Output: false } 96 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS } Listing 2-27’s overriding equals() method begins with an if statement that uses the instanceof operator to determine whether the argument passed to parameter o is an instance of the Point class. If not, the if statement executes return false;. The o instanceof Point expression satisfies the last portion of the contract: For any nonnull reference value x, x.equals(null) returns false. Because the null reference is not an instance of any class, passing this value to equals() causes the expression to evaluate to false. The o instanceof Point expression also prevents a java.lang.ClassCastException instance from being thrown via expression (Point) o in the event that you pass an object other than a Point object to equals(). (I will discuss exceptions in the next chapter.) Following the cast, the contract’s reflexivity, symmetry, and transitivity requirements are met by only allowing Points to be compared with other Points, via expression p.x == x && p.y == y. The final contract requirement, consistency, is met by making sure that the equals() method is deterministic. In other words, this method does not rely on any field value that could change from method call to method call. ■ Tip You can optimize the performance of a time-consuming equals() method by first using == to determine if o’s reference identifies the current object. Simply specify if (o == this) return true; as the equals() method’s first statement. This optimization is not necessary in Listing 2-27’s equals() method, which has satisfactory performance. It is important to always override the hashCode() method when overriding equals(). I did not do so in Listing 2-27 because I have yet to formally introduce hashCode(). Finalization Finalization refers to cleanup via the finalize() method, which is known as a finalizer. The finalize() method’s Java documentation states that finalize() is “called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize() method to dispose of system resources or to perform other cleanup.” Object’s version of finalize() does nothing; you must override this method with any needed cleanup code. Because the JVM might never call finalize() before an application terminates, you should provide an explicit cleanup method, and have finalize() call this method as a safety net in case the method is not otherwise called. ■ Caution Never depend on finalize() for releasing limited resources such as graphics contexts or file descriptors. For example, if an application object opens files, expecting that its finalize() method will close them, the application might find itself unable to open additional files when a tardy JVM is slow to call finalize(). What makes this problem worse is that finalize() might be called more frequently on another JVM, resulting in 97 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS this too-many-open-files problem not revealing itself. The developer might thus falsely believe that the application behaves consistently across different JVMs. If you decide to override finalize(), your object’s subclass layer must give its superclass layer an opportunity to perform finalization. You can accomplish this task by specifying super.finalize(); as the last statement in your method, which the following example demonstrates: @Override protected void finalize() throws Throwable { try { // Perform subclass cleanup. } finally { super.finalize(); } } The example’s finalize() declaration appends throws Throwable to the method header because the cleanup code might throw an exception. If an exception is thrown, execution leaves the method and, in the absence of try-finally, super.finalize(); never executes. (I will discuss exceptions and try-finally in Chapter 3.) To guard against this possibility, the subclass’s cleanup code executes in a block that follows reserved word try. If an exception is thrown, Java’s exception-handling logic executes the block following the finally reserved word, and super.finalize(); executes the superclass’s finalize() method. The finalize() method has often been used to perform resurrection (making an unreferenced object referenced), to implement object pools that recycle the same objects when these objects are expensive (time-wise) to create (database connection objects are an example). Resurrection occurs when you assign this (a reference to the current object) to a class or instance field (or to another long-lived variable). For example, you might specify r = this; within finalize() to assign the unreferenced object identified as this to a class field named r. Because of the possibility for resurrection, there is a severe performance penalty imposed on the garbage collection of an object that overrides finalize(). You’ll learn about this penalty and a better alternative to overriding finalize() in Chapter 4. ■ Note A resurrected object’s finalizer cannot be called again. Hash Codes The hashCode() method returns a 32-bit integer that identifies the current object’s hash code, a small value that results from applying a mathematical function to a potentially large amount of data. The calculation of this value is known as hashing. 98 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS This book was purchased by [email protected] You must override hashCode() when overriding equals(), and in accordance with the following contract, which is specified in hashCode()’s Java documentation: • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode() method must consistently return the same integer, provided no information used in equals(Object) comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application. • If two objects are equal according to the equals(Object) method, then calling the hashCode() method on each of the two objects must produce the same integer result. • It is not required that if two objects are unequal according to the equals(Object) method, then calling the hashCode() method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects might improve the performance of hash tables. Fail to obey this contract and your class’s instances will not work properly with Java’s hash-based Collections Framework classes, such as java.util.HashMap. (I will discuss HashMap and other Collections Framework classes in Chapter 5.) If you override equals() but not hashCode(), you most importantly violate the second item in the contract: The hash codes of equal objects must also be equal. This violation can lead to serious consequences, as demonstrated in the following example: java.util.Map<Point, String> map = new java.util.HashMap<>(); map.put(p1, "first point"); System.out.println(map.get(p1)); // Output: first point System.out.println(map.get(new Point(10, 20))); // Output: null Assume that the example’s statements are appended to Listing 2-27’s main() method—the java.util. prefix, <Point, String>, and <> have to do with packages and generics, which I discuss in Chapter 3. After main() creates its Point objects and calls its System.out.println() methods, it executes this example’s statements, which perform the following tasks: • The first statement instantiates HashMap, which is in the java.util package. • The second statement calls HashMap’s put() method to store Listing 2-27’s p1 object key and the "first point" value in the hashmap. • The third statement retrieves the value of the hashmap entry whose Point key is logically equal to p1 via HashMap’s get() method. • The fourth statement is equivalent to the third statement, but returns the null reference instead of "first point". Although objects p1 and Point(10, 20) are logically equivalent, these objects have different hash codes, resulting in each object referring to a different entry in the hashmap. If an object is not stored (via put()) in that entry, get() returns null. Correcting this problem requires that hashCode() be overridden to return the same integer value for logically equivalent objects. I’ll show you how to accomplish this task when I discuss HashMap in Chapter 5. 99 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS String Representation The toString() method returns a string-based representation of the current object. This representation defaults to the object’s class name, followed by the @ symbol, followed by a hexadecimal representation of the object’s hash code. For example, if you were to execute System.out.println(p1); to output Listing 2-27’s p1 object, you would see a line of output similar to [email protected] (System.out.println() calls p1’s inherited toString() method behind the scenes.) You should strive to override toString() so that it returns a concise but meaningful description of the object. For example, you might declare, in Listing 2-27’s Point class, a toString() method that is similar to the following: @Override public String toString() { return "("+x+", "+y+")"; } This time, executing System.out.println(p1); results in more meaningful output, such as (10, 20). Composition Implementation inheritance and composition offer two different approaches to reusing code. As you have learned, implementation inheritance is concerned with extending a class with a new class, which is based upon an “is-a” relationship between them: a Car is a Vehicle, for example. On the other hand, composition is concerned with composing classes out of other classes, which is based upon a “has-a” relationship between them. For example, a Car has an Engine, Wheels, and a SteeringWheel. You have already seen examples of composition in this chapter. For example, Listing 2-3’s Car class includes String make and String model fields. Listing 2-28’s Car class provides another example of composition. Listing 2-28. A Car class whose instances are composed of other objects class Car extends Vehicle { private Engine engine; private Wheel[] wheels; private SteeringWheel steeringWheel; } Listing 2-28 demonstrates that composition and implementation inheritance are not mutually exclusive. Although not shown, Car inherits various members from its Vehicle superclass, in addition to providing its own engine, wheels, and steeringwheel instance fields. The Trouble with Implementation Inheritance Implementation inheritance is potentially dangerous, especially when the developer does not have complete control over the superclass, or when the superclass is not designed and documented with extension in mind. 100 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS The problem is that implementation inheritance breaks encapsulation. The subclass relies on implementation details in the superclass. If these details change in a new version of the superclass, the subclass might break, even if the subclass is not touched. For example, suppose you have purchased a library of Java classes, and one of these classes describes an appointment calendar. Although you do not have access to this class’s source code, assume that Listing 2-29 describes part of its code. Listing 2-29. An appointment calendar class public class ApptCalendar { private final static int MAX_APPT = 1000; private Appt[] appts; private int size; public ApptCalendar() { appts = new Appt[MAX_APPT]; size = 0; // redundant because field automatically initialized to 0 // adds clarity, however } public void addAppt(Appt appt) { if (size == appts.length) return; // array is full appts[size++] = appt; } public void addAppts(Appt[] appts) { for (int i = 0; i < appts.length; i++) addAppt(appts[i]); } } Listing 2-29’s ApptCalendar class stores an array of appointments, with each appointment described by an Appt instance. For this discussion, Appt’s details are irrelevant—it could be as trivial as class Appt {}. Suppose you want to log each appointment in a file. Because a logging capability is not provided, you extend ApptCalendar with Listing 2-30’s LoggingApptCalendar class, which adds logging behavior in overriding addAppt() and addAppts() methods. 101 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-30. Extending the appointment calendar class public class LoggingApptCalendar extends ApptCalendar { // A constructor is not necessary because the Java compiler will add a // noargument constructor that calls the superclass's noargument // constructor by default. @Override public void addAppt(Appt appt) { Logger.log(appt.toString()); super.addAppt(appt); } @Override public void addAppts(Appt[] appts) { for (int i = 0; i < appts.length; i++) Logger.log(appts[i].toString()); super.addAppts(appts); } } Listing 2-30’s LoggingApptCalendar class relies on a Logger class whose void log(String msg) class method logs a string to a file (the details are unimportant). Notice the use of toString() to convert an Appt object to a String object, which is then passed to log(). Although this class looks okay, it does not work as you might expect. Suppose you instantiate this class and add a few Appt instances to this instance via addAppts(), in the following manner: LoggingApptCalendar lapptc = new LoggingApptCalendar(); lapptc.addAppts(new Appt[] {new Appt(), new Appt(), new Appt()}); If you also add a System.out.println(msg); method call to Logger’s log(String msg) method, to output this method’s argument to standard output, you will discover that log() outputs a total of six messages; each of the expected three messages (one per Appt object) is duplicated. When LoggingApptCalendar’s addAppts() method is called, it first calls Logger.log() for each Appt instance in the appts array that is passed to addAppts(). This method then calls ApptCalendar’s addAppts() method via super.addAppts(appts);. ApptCalendar’s addAppts() method calls LoggingApptCalendar’s overriding addAppt() method for each Appt instance in its appts array argument. addAppt() executes Logger.log(appt.toString()); to log its appt argument’s string representation, and you end up with three additional logged messages. If you did not override the addAppts() method, this problem would go away. However, the subclass would be tied to an implementation detail: ApptCalendar’s addAppts() method calls addAppt(). It is not a good idea to rely on an implementation detail when the detail is not documented. (I previously stated that you do not have access to ApptCalendar’s source code.) When a detail is not documented, it can change in a new version of the class. Because a base class change can break a subclass, this problem is known as the fragile base class problem. A related cause of fragility that also has to do with overriding methods occurs when new methods are added to a superclass in a subsequent release. For example, suppose a new version of the library introduces a new public void addAppt(Appt appt, boolean unique) method into the ApptCalendar class. This method adds the appt instance to the 102 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS calendar when unique is false, and, when unique is true, it adds the appt instance only if it has not previously been added. Because this method has been added after the LoggingApptCalendar class was created, LoggingApptCalendar does not override the new addAppt() method with a call to Logger.log(). As a result, Appt instances passed to the new addAppt() method are not logged. Here is another problem: You introduce a method into the subclass that is not also in the superclass. A new version of the superclass presents a new method that matches the subclass method signature and return type. Your subclass method now overrides the superclass method, and probably does not fulfill the superclass method’s contract. There is a way to make these problems disappear. Instead of extending the superclass, create a private field in a new class, and have this field reference an instance of the “superclass.” This task demonstrates composition because you are forming a “has-a” relationship between the new class and the “superclass.” Additionally, have each of the new class’s instance methods call the corresponding “superclass” method via the “superclass” instance that was saved in the private field, and also return the called method’s return value. This task is known as forwarding, and the new methods are known as forwarding methods. Listing 2-31 presents an improved LoggingApptCalendar class that uses composition and forwarding to forever eliminate the fragile base class problem and the additional problem of the unanticipated method overriding. Listing 2-31. A composed logging appointment calendar class public class LoggingApptCalendar { private ApptCalendar apptCal; public LoggingApptCalendar(ApptCalendar apptCal) { this.apptCal = apptCal; } public void addAppt(Appt appt) { Logger.log(appt.toString()); apptCal.addAppt(appt); } public void addAppts(Appt[] appts) { for (int i = 0; i < appts.length; i++) Logger.log(appts[i].toString()); apptCal.addAppts(appts); } } Listing 2-31’s LoggingApptCalendar class does not depend upon implementation details of the ApptCalendar class. You can add new methods to ApptCalendar and they will not break LoggingApptCalendar. 103 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS ■ Note Listing 2-31’s LoggingApptCalendar class is an example of a wrapper class, a class whose instances wrap other instances. Each LoggingApptCalendar instance wraps an ApptCalendar instance. LoggingApptCalendar is also an example of the Decorator design pattern, which is presented on page 175 of Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1995; ISBN: 0201633612). When should you extend a class and when should you use a wrapper class? Extend a class when an “is-a” relationship exists between the superclass and the subclass, and either you have control over the superclass or the superclass has been designed and documented for class extension. Otherwise, use a wrapper class. What does “design and document for class extension” mean? Design means provide protected methods that hook into the class’s inner workings (to support writing efficient subclasses), and ensure that constructors and the clone() method never call overridable methods. Document means clearly state the impact of overriding methods. ■ Caution Wrapper classes should not be used in a callback framework, an object framework in which an object passes its own reference to another object (via this) so that the latter object can call the former object’s methods at a later time. This “calling back to the former object’s method” is known as a callback. Because the wrapped object does not know of its wrapper class, it passes only its reference (via this), and resulting callbacks do not involve the wrapper class’s methods. Changing Form Some real-world entities can change their forms. For example, water (on Earth as opposed to interstellar space) is naturally a liquid, but it changes to a solid when frozen, and it changes to a gas when heated to its boiling point. Insects such as butterflies that undergo metamorphosis are another example. The ability to change form is known as polymorphism, and is useful to model in a programming language. For example, code that draws arbitrary shapes can be expressed more concisely by introducing a single Shape class and its draw() method, and by invoking that method for each Circle instance, Rectangle instance, and other Shape instance stored in an array. When Shape’s draw() method is called for an array instance, it is the Circle’s, Rectangle’s or other Shape instance’s draw() method that gets called. We say that there are many forms of Shape’s draw() method, or that this method is polymorphic. Java supports four kinds of polymorphism: 104 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS • Coercion: An operation serves multiple types through implicit type conversion. For example, division lets you divide an integer by another integer, or divide a floating-point value by another floating-point value. If one operand is an integer and the other operand is a floating-point value, the compiler coerces (implicitly converts) the integer to a floating-point value, to prevent a type error. (There is no division operation that supports an integer operand and a floating-point operand.) Passing a subclass object reference to a method’s superclass parameter is another example of coercion polymorphism. The compiler coerces the subclass type to the superclass type, to restrict operations to those of the superclass. • Overloading: The same operator symbol or method name can be used in different contexts. For example, + can be used to perform integer addition, floating-point addition, or string concatenation, depending on the types of its operands. Also, multiple methods having the same name can appear in a class (through declaration and/or inheritance). • Parametric: Within a class declaration, a field name can associate with different types and a method name can associate with different parameter and return types. The field and method can then take on different types in each class instance. For example, a field might be of type java.lang.Integer and a method might return an Integer reference in one class instance, and the same field might be of type String and the same method might return a String reference in another class instance. Java supports parametric polymorphism via generics, which I will discuss in Chapter 3. • Subtype: A type can serve as another type’s subtype. When a subtype instance appears in a supertype context, executing a supertype operation on the subtype instance results in the subtype’s version of that operation executing. For example, suppose that Circle is a subclass of Point, and that both classes contain a draw() method. Assigning a Circle instance to a variable of type Point, and then calling the draw() method via this variable, results in Circle’s draw() method being called. Subtype polymorphism partners with implementation inheritance. Many developers do not regard coercion and overloading as valid kinds of polymorphism. They see coercion and overloading as nothing more than type conversions and syntactic sugar (syntax that simplifies a language, making it “sweeter” to use). In contrast, parametric and subtype are regarded as valid kinds of polymorphism. This section introduces you to subtype polymorphism through upcasting and late binding. We then move on to abstract classes and abstract methods, downcasting and runtime type identification, and covariant return types. Upcasting and Late Binding Listing 2-27’s Point class represents a point as an x-y pair. Because a circle (in this example) is an x-y pair denoting its center, and has a radius denoting its extent, you can extend Point with a Circle class that introduces a radius field. Check out Listing 2-32. Listing 2-32. A Circle class extending the Point class class Circle extends Point { private int radius; 105 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Circle(int x, int y, int radius) { super(x, y); this.radius = radius; } int getRadius() { return radius; } } Listing 2-32’s Circle class describes a Circle as a Point with a radius, which implies that you can treat a Circle instance as if it was a Point instance. Accomplish this task by assigning the Circle instance to a Point variable, as demonstrated here: Circle c = new Circle(10, 20, 30); Point p = c; The cast operator is not needed to convert from Circle to Point because access to a Circle instance via Point’s interface is legal. After all, a Circle is at least a Point. This assignment is known as upcasting because you are implicitly casting up the type hierarchy (from the Circle subclass to the Point superclass). It is also an example of covariance in that a type with a wider range of values (Circle) is being converted to a type with a narrower range of values (Point). After upcasting Circle to Point, you cannot call Circle’s getRadius() method because this method is not part of Point’s interface. Losing access to subtype features after narrowing it to a superclass seems useless, but is necessary for achieving subtype polymorphism. In addition to upcasting the subclass instance to a variable of the superclass type, subtype polymorphism involves declaring a method in the superclass and overriding this method in the subclass. For example, suppose Point and Circle are to be part of a graphics application, and you need to introduce a draw() method into each class to draw a point and a circle, respectively. You end with the class structure shown in Listing 2-33. Listing 2-33. Declaring a graphics application’s Point and Circle classes class Point { private int x, y; Point(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } @Override public String toString() 106 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS { return "("+x+", "+y+")"; } void draw() { System.out.println("Point drawn at "+toString ()); } } class Circle extends Point { private int radius; Circle(int x, int y, int radius) { super(x, y); this.radius = radius; } int getRadius() { return radius; } @Override public String toString() { return ""+radius; } @Override void draw() { System.out.println("Circle drawn at "+super.toString()+ " with radius "+toString()); } } Listing 2-33’s draw() methods will ultimately draw graphics shapes, but simulating their behaviors via System.out.println() method calls is sufficient during the early testing phase of the graphics application. Now that you have temporarily finished with Point and Circle, you want to test their draw() methods in a simulated version of the graphics application. To achieve this objective, you write Listing 2-34’s Graphics class. Listing 2-34. A Graphics class for testing Point’s and Circle’s draw() methods class Graphics { public static void main(String[] args) { Point[] points = new Point[] { new Point(10, 20), new Circle(10, 20, 30) }; for (int i = 0; i < points.length; i++) points[i].draw(); } } 107 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-34’s main() method first declares an array of Points. Upcasting is demonstrated by first having the array’s initializer instantiate the Circle class, and then by assigning this instance’s reference to the second element in the points array. Moving on, main() uses a for loop to call each Point element’s draw() method. Because the first iteration calls Point’s draw() method, whereas the second iteration calls Circle’s draw() method, you observe the following output: Point drawn at (10, 20) Circle drawn at (10, 20) with radius 30 How does Java “know” that it must call Circle’s draw() method on the second loop iteration? Should it not call Point’s draw() method because Circle is being treated as a Point thanks to the upcast? At compile time, the compiler does not know which method to call. All it can do is verify that a method exists in the superclass, and verify that the method call’s arguments list and return type match the superclass’s method declaration. In lieu of knowing which method to call, the compiler inserts an instruction into the compiled code that, at runtime, fetches and uses whatever reference is in points[1] to call the correct draw() method. This task is known as late binding. Late binding is used for calls to non-final instance methods. For all other method calls, the compiler knows which method to call, and inserts an instruction into the compiled code that calls the method associated with the variable’s type (not its value). This task is known as early binding. You can also upcast from one array to another provided that the array being upcast is a subtype of the other array. Consider Listing 2-35. Listing 2-35. Demonstrating array upcasting class Point { private int x, y; Point(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } } class ColoredPoint extends Point { private int color; ColoredPoint(int x, int y, int color) { super(x, y); this.color = color; } int getColor() { return color; } } class UpcastArrayDemo { public static void main(String[] args) { ColoredPoint[] cptArray = new ColoredPoint[1]; 108 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS cptArray[0] = new ColoredPoint(10, 20, 5); Point[] ptArray = cptArray; System.out.println(ptArray[0].getX()); // Output: 10 System.out.println(ptArray[0].getY()); // Output: 20 System.out.println(ptArray[0].getColor()); // Illegal // } } Listing 2-35’s main() method first creates a ColoredPoint array consisting of one element. It then instantiates this class and assigns the object’s reference to this element. Because ColoredPoint[] is a subtype of Point[], main() is able to upcast cptArray’s ColoredPoint[] type to Point[] and assign its reference to ptArray. main() then invokes the ColoredPoint instance’s getX() and getY() methods via ptArray[0]. It cannot invoke getColor() because ptArray has narrower scope than cptArray. In other words, getColor() is not part of Point’s interface. This book was purchased by [email protected] Abstract Classes and Abstract Methods Suppose new requirements dictate that your graphics application must include a Rectangle class. Also, this class must include a draw() method, and this method must be tested in a manner similar to that shown in Listing 2-34’s Graphics class. In contrast to Circle, which is a Point with a radius, it does not make sense to think of a Rectangle as a being a Point with a width and height. Rather, a Rectangle instance would probably be composed of a Point indicating its origin and a Point indicating its width and height extents. Because circles, points, and rectangles are examples of shapes, it makes more sense to declare a Shape class with its own draw() method than to specify class Rectangle extends Point. Listing 2-36 presents Shape’s declaration. Listing 2-36. Declaring a Shape class class Shape { void draw() { } } Listing 2-36’s Shape class declares an empty draw() method that only exists to be overridden and to demonstrate subtype polymorphism. You can now refactor Listing 2-33’s Point class to extend Listing 2-36’s Shape class, leave Circle as is, and introduce a Rectangle class that extends Shape. You can then refactor Listing 2-34’s Graphics class’s main() method to take Shape into account. Check out the following main() method: public static void main(String[] args) { Shape[] shapes = new Shape[] { new Point(10, 20), new Circle(10, 20, 30), new Rectangle(20, 30, 15, 25) }; for (int i = 0; i < shapes.length; i++) shapes[i].draw(); } Because Point and Rectangle directly extend Shape, and because Circle indirectly extends Shape by extending Point, main() responds to shapes[i].draw(); by calling the correct subclass’s draw() method. 109 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Although Shape makes the code more flexible, there is a problem. What is to stop someone from instantiating Shape and adding this meaningless instance to the shapes array, as follows? Shape[] shapes = new Shape[] { new Point(10, 20), new Circle(10, 20, 30), new Rectangle(20, 30, 15, 25), new Shape() }; What does it mean to instantiate Shape? Because this class describes an abstract concept, what does it mean to draw a generic shape? Fortunately, Java provides a solution to this problem, which is demonstrated in Listing 2-37. Listing 2-37. Abstracting the Shape class abstract class Shape { abstract void draw(); // semicolon is required } Listing 2-37 uses Java’s abstract reserved word to declare a class that cannot be instantiated. The compiler reports an error should you try to instantiate this class. ■ Tip Get into the habit of declaring classes that describe generic categories (e.g., shape, animal, vehicle, and account) abstract. This way, you will not inadvertently instantiate them. The abstract reserved word is also used to declare a method without a body—the compiler reports an error when you supply a body or omit the semicolon. The draw() method does not need a body because it cannot draw an abstract shape. ■ Caution The compiler reports an error when you attempt to declare a class that is both abstract and final. For example, abstract final class Shape is an error because an abstract class cannot be instantiated and a final class cannot be extended. The compiler also reports an error when you declare a method to be abstract but do not declare its class to be abstract. For example, removing abstract from the Shape class’s header in Listing 2-37 results in an error. This removal is an error because a non-abstract (concrete) class cannot be instantiated when it contains an abstract method. Finally, when you extend an abstract class, the extending class must override all the abstract class’s abstract methods, or else the extending class must itself be declared to be abstract; otherwise, the compiler will report an error. An abstract class can contain non-abstract methods in addition to or instead of abstract methods. For example, Listing 2-22’s Vehicle class could have been declared abstract. The constructor would still be present, to initialize private fields, even though you could not instantiate the resulting class. 110 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Downcasting and Runtime Type Identification Moving up the type hierarchy via upcasting causes loss of access to subtype features. For example, assigning a Circle instance to Point variable p means that you cannot use p to call Circle’s getRadius() method. However, it is possible to once again access the Circle instance’s getRadius() method by performing an explicit cast operation; for example, Circle c = (Circle) p;. This assignment is known as downcasting because you are explicitly moving down the type hierarchy (from the Point superclass to the Circle subclass). It is also an example of contravariance in that a type with a narrower range of values (Point) is being converted to a type with a wider range of values (Circle). Although an upcast is always safe (the superclass’s interface is a subset of the subclass’s interface), the same cannot be said of a downcast. Listing 2-38 shows you what kind of trouble you can get into when downcasting is used incorrectly. Listing 2-38. The trouble with downcasting class A { } class B extends A { void m() {} } class DowncastDemo { public static void main(String[] args) { A a = new A(); B b = (B) a; b.m(); } } Listing 2-38 presents a class hierarchy consisting of a superclass named A and a subclass named B. Although A does not declare any members, B declares a single m() method. A third class named DowncastDemo provides a main() method that first instantiates A, and then tries to downcast this instance to B and assign the result to variable b. The compiler will not complain because downcasting from a superclass to a subclass in the same type hierarchy is legal. However, if the assignment is allowed, the application will undoubtedly crash when it tries to execute b.m();. The crash happens because the JVM will attempt to call a method that does not exist— class A does not have an m() method. Fortunately, this scenario will never happen because the JVM verifies that the cast is legal. Because it detects that A does not have an m() method, it does not permit the cast by throwing an instance of the ClassCastException class. The JVM’s cast verification illustrates runtime type identification (or RTTI, for short). Cast verification performs RTTI by examining the type of the cast operator’s operand to see whether the cast should be allowed. Clearly, the cast should not be allowed. A second form of RTTI involves the instanceof operator. This operator checks the left operand to see whether it is an instance of the right operand, and returns true if this is the case. The following example introduces instanceof to Listing 2-38 to prevent the ClassCastException: if (a instanceof B) 111 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS { B b = (B) a; b.m(); } The instanceof operator detects that variable a’s instance was not created from B and returns false to indicate this fact. As a result, the code that performs the illegal cast will not execute. (Overuse of instanceof probably indicates poor software design.) Because a subtype is a kind of supertype, instanceof will return true when its left operand is a subtype instance or a supertype instance of its right operand supertype. The following example demonstrates: A a = new A(); B b = new B(); System.out.println(b instanceof A); // Output: true System.out.println(a instanceof A); // Output: true This example assumes the class structure shown in Listing 2-38 and instantiates superclass A and subclass B. The first System.out.println() method call outputs true because b’s reference identifies an instance of a subclass of A; the second System.out.println() method call outputs true because a’s reference identifies an instance of superclass A. You can also downcast from one array to another provided that the array being downcast is a supertype of the other array, and whose elements types are those of the subtype. Consider Listing 2-39. Listing 2-39. Demonstrating array downcasting class Point { private int x, y; Point(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } } class ColoredPoint extends Point { private int color; ColoredPoint(int x, int y, int color) { super(x, y); this.color = color; } int getColor() { return color; } } class DowncastArrayDemo { public static void main(String[] args) { ColoredPoint[] cptArray = new ColoredPoint[1]; cptArray[0] = new ColoredPoint(10, 20, 5); 112 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Point[] ptArray = cptArray; System.out.println(ptArray[0].getX()); // Output: 10 System.out.println(ptArray[0].getY()); // Output: 20 System.out.println(ptArray[0].getColor()); // Illegal if (ptArray instanceof ColoredPoint[]) { ColoredPoint cp = (ColoredPoint) ptArray[0]; System.out.println(cp.getColor()); } // } } Listing 2-39 is similar to Listing 2-35 except that it also demonstrates downcasting. Notice its use of instanceof to verify that ptArray’s referenced object is of type ColoredPoint[]. If this operator returns true, it is safe to downcast ptArray[0] from Point to ColoredPoint and assign the reference to ColoredPoint. So far, you have encountered two forms of RTTI. Java also supports a third form that is known as reflection. I will introduce you to this form of RTTI when I cover reflection in Chapter 4. Covariant Return Types A covariant return type is a method return type that, in the superclass’s method declaration, is the supertype of the return type in the subclass’s overriding method declaration. Listing 2-40 demonstrates this feature. Listing 2-40. A demonstration of covariant return types class SuperReturnType { @Override public String toString() { return "superclass return type"; } } class SubReturnType extends SuperReturnType { @Override public String toString() { return "subclass return type"; } } class Superclass { SuperReturnType createReturnType() { return new SuperReturnType(); } } class Subclass extends Superclass { 113 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS @Override SubReturnType createReturnType() { return new SubReturnType(); } } class CovarDemo { public static void main(String[] args) { SuperReturnType suprt = new Superclass().createReturnType(); System.out.println(suprt); // Output: superclass return type SubReturnType subrt = new Subclass().createReturnType(); System.out.println(subrt); // Output: subclass return type } } Listing 2-40 declares SuperReturnType and Superclass superclasses, and SubReturnType and Subclass subclasses; each of Superclass and Subclass declares a createReturnType() method. Superclass’s method has its return type set to SuperReturnType, whereas Subclass’s overriding method has its return type set to SubReturnType, a subclass of SuperReturnType. Covariant return types minimize upcasting and downcasting. For example, Subclass’s createReturnType() method does not need to upcast its SubReturnType instance to its SubReturnType return type. Furthermore, this instance does not need to be downcast to SubReturnType when assigning to variable subrt. In the absence of covariant return types, you would end up with Listing 2-41. Listing 2-41. Upcasting and downcasting in the absence of covariant return types class SuperReturnType { @Override public String toString() { return "superclass return type"; } } class SubReturnType extends SuperReturnType { @Override public String toString() { return "subclass return type"; } } class Superclass { SuperReturnType createReturnType() { return new SuperReturnType(); } } 114 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS class Subclass extends Superclass { @Override SuperReturnType createReturnType() { return new SubReturnType(); } } class CovarDemo { public static void main(String[] args) { SuperReturnType suprt = new Superclass().createReturnType(); System.out.println(suprt); // Output: superclass return type SubReturnType subrt = (SubReturnType) new Subclass().createReturnType(); System.out.println(subrt); // Output: subclass return type } } In Listing 2-41, the first bolded code reveals an upcast from SubReturnType to SuperReturnType, and the second bolded code uses the required (SubReturnType) cast operator to downcast from SuperReturnType to SubReturnType, prior to the assignment to subrt. Formalizing Class Interfaces In my introduction to information hiding, I stated that every class X exposes an interface (a protocol consisting of constructors, methods, and [possibly] fields that are made available to objects created from other classes for use in creating and communicating with X’s objects). Java formalizes the interface concept by providing reserved word interface, which is used to introduce a type without implementation. Java also provides language features to declare, implement, and extend interfaces. After looking at interface declaration, implementation, and extension, this section explains the rationale for using interfaces. Declaring Interfaces An interface declaration consists of a header followed by a body. At minimum, the header consists of reserved word interface followed by a name that identifies the interface. The body starts with an open brace character and ends with a close brace. Sandwiched between these delimiters are constant and method header declarations. Consider Listing 2-42. Listing 2-42. Declaring a Drawable interface interface Drawable { int RED = 1; // For simplicity, integer constants are used. These int GREEN = 2; // constants are not that descriptive, as you will see. int BLUE = 3; int BLACK = 4; void draw(int color); } 115 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-42 declares an interface named Drawable. By convention, an interface’s name begins with an uppercase letter. Also, the first letter of each subsequent word in a multiword interface name is capitalized. ■ Note Many interface names end with the able suffix. For example, Java’s standard class library includes interfaces named Adjustable, Callable, Comparable, Cloneable, Iterable, Runnable, and Serializable. It’s not mandatory to use this suffix; the standard class library also provides interfaces named CharSequence, Collection, Composite, Executor, Future, Iterator, List, Map, and Set. Drawable declares four fields that identify color constants. Drawable also declares a draw() method that must be called with one of these constants to specify the color used to draw something. ■ Note You can precede interface with public, to make your interface accessible to code outside of its package. (I will discuss packages in Chapter 3.) Otherwise, the interface is only accessible to other types in its package. You can also precede interface with abstract, to emphasize that an interface is abstract. Because an interface is already abstract, it is redundant to specify abstract in the interface’s declaration. An interface’s fields are implicitly declared public, static, and final. It is therefore redundant to declare them with these reserved words. Because these fields are constants, they must be explicitly initialized; otherwise, the compiler reports an error. Finally, an interface’s methods are implicitly declared public and abstract. Therefore, it is redundant to declare them with these reserved words. Because these methods must be instance methods, do not declare them static or the compiler will report errors. Drawable identifies a type that specifies what to do (draw something) but not how to do it. It leaves implementation details to classes that implement this interface. Instances of such classes are known as drawables because they know how to draw themselves. ■ Note An interface that declares no members is known as a marker interface or a tagging interface. It associates metadata with a class. For example, the Cloneable marker/tagging interface states that instances of its implementing class can be shallowly cloned. RTTI is used to detect that an object’s class implements a marker/tagging interface. For example, when Object’s clone() method detects, via RTTI, that the calling instance’s class implements Cloneable, it shallowly clones the object. 116 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Implementing Interfaces By itself, an interface is useless. To be of any benefit to an application, the interface needs to be implemented by a class. Java provides the implements reserved word for this task. Listing 2-43 demonstrates using implements to implement the aforementioned Drawable interface. Listing 2-43. Implementing the Drawable interface class Point implements Drawable { private int x, y; Point(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } @Override public String toString() { return "("+x+", "+y+")"; } @Override public void draw(int color) { System.out.println("Point drawn at "+toString()+" in color "+color); } } class Circle extends Point implements Drawable { private int radius; Circle(int x, int y, int radius) { super(x, y); this.radius = radius; } int getRadius() { return radius; } @Override public String toString() { return ""+radius; } 117 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS @Override public void draw(int color) { System.out.println("Circle drawn at "+super.toString()+ " with radius "+toString()+" in color "+color); } } Listing 2-43 retrofits Listing 2-33’s class hierarchy to take advantage of Listing 2-42’s Drawable interface. You will notice that each of classes Point and Circle implements this interface by attaching the implements Drawable clause to its class header. To implement an interface, the class must specify, for each interface method header, a method whose header has the same signature and return type as that in the interface’s method header, and a code body to go with the method header. ■ Caution When implementing a method, do not forget that the interface’s methods are implicitly declared public. If you forget to include public in the implemented method’s declaration, the compiler will report an error because you are attempting to assign weaker access to the implemented method. When a class implements an interface, the class inherits the interface’s constants and method headers, and overrides the method headers by providing implementations (hence the @Override annotation). This is known as interface inheritance. It turns out that Circle’s header does not need the implements Drawable clause. If this clause is not present, Circle inherits Point’s draw() method, and is still considered to be a Drawable, whether or not it overrides this method. An interface specifies a type whose data values are the objects whose classes implement the interface, and whose behaviors are those specified by the interface. This fact implies that you can assign an object’s reference to a variable of the interface type, provided that the object’s class implements the interface. The following example provides a demonstration: public static void main(String[] args) { Drawable[] drawables = new Drawable[] { new Point(10, 20), new Circle(10, 20, 30) }; for (int i = 0; i < drawables.length; i++) drawables[i].draw(Drawable.RED); } Because Point and Circle instances are drawables by virtue of these classes implementing the Drawable interface, it is legal to assign Point and Circle instance references to variables (including array elements) of type Drawable. When you run this method, it generates the following output: Point drawn at (10, 20) in color 1 Circle drawn at (10, 20) with radius 30 in color 1 118 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-42’s Drawable interface is useful for drawing a shape’s outline. Suppose you also need to fill a shape’s interior. You might attempt to satisfy this requirement by declaring Listing 2-44’s Fillable interface. Listing 2-44. Declaring a Fillable interface interface Fillable { int RED = 1; int GREEN = 2; int BLUE = 3; int BLACK = 4; void fill(int color); } This book was purchased by [email protected] Given Listings 2-42 and 2-44, you can declare that the Point and Circle classes implement both interfaces by specifying class Point implements Drawable, Fillable and class Circle implements Drawable, Fillable. You can then modify the main() method to also treat the drawables as fillables so that you can fill these shapes, as follows: public static void main(String[] args) { Drawable[] drawables = new Drawable[] { new Point(10, 20), new Circle(10, 20, 30) }; for (int i = 0; i < drawables.length; i++) drawables[i].draw(Drawable.RED); Fillable[] fillables = new Fillable[drawables.length]; for (int i = 0; i < drawables.length; i++) { fillables[i] = (Fillable) drawables[i]; fillables[i].fill(Fillable.GREEN); } } After invoking each drawable’s draw() method, main() creates a Fillable array of the same length as the Drawable array. It then proceeds to copy each Drawable array element to a Fillable array element, and then invoke the fillable’s fill() method. The (Fillable) cast is necessary because a drawable is not a fillable. This cast operation will succeed because the Point and Circle instances being copied implement Fillable as well as Drawable. ■ Tip You can list as many interfaces as you need to implement by specifying a comma-separated list of interface names after implements. Implementing multiple interfaces can lead to name collisions, and the compiler will report errors. For example, suppose that you attempt to compile Listing 2-45’s interface and class declarations. 119 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-45. Colliding interfaces interface A { int X = 1; void foo(); } interface B { int X = 1; int foo(); } class Collision implements A, B { @Override public void foo(); @Override public int foo() { return X; } } Each of Listing 2-45’s A and B interfaces declares a constant named X. Despite each constant having the same type and value, the compiler will report an error when it encounters X in Collision’s second foo() method because it does not know which X is being inherited. Speaking of foo(), the compiler reports an error when it encounters Collision’s second foo() declaration because foo() has already been declared. You cannot overload a method by changing only its return type. The compiler will probably report additional errors. For example, the Java 7 compiler has this to say when told to compile Listing 2-45: Collision.java:16: error: foo() is already defined in Collision public int foo() { return X; } ^ Collision.java:11: error: Collision is not abstract and does not override abstract method foo() in B class Collision implements A, B ^ Collision.java:14: error: foo() in Collision cannot implement foo() in B public void foo(); ^ return type void is not compatible with int Collision.java:16: error: reference to X is ambiguous, both variable X in A and variable X in B match public int foo() { return X; } ^ 4 errors Extending Interfaces Just as a subclass can extend a superclass via reserved word extends, you can use this reserved word to have a subinterface extend a superinterface. This, too, is known as interface inheritance. 120 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS For example, the duplicate color constants in Drawable and Fillable lead to name collisions when you specify their names by themselves in an implementing class. To avoid these name collisions, prefix a name with its interface name and the member access operator, or place these constants in their own interface, and have Drawable and Fillable extend this interface, as demonstrated in Listing 2-46. Listing 2-46. Extending the Colors interface interface Colors { int RED = 1; int GREEN = 2; int BLUE = 3; int BLACK = 4; } interface Drawable extends Colors { void draw(int color); } interface Fillable extends Colors { void fill(int color); } The fact that Drawable and Fillable each inherit constants from Colors is not a problem for the compiler. There is only a single copy of these constants (in Colors) and no possibility of a name collision, and so the compiler is satisfied. If a class can implement multiple interfaces by declaring a comma-separated list of interface names after implements, it seems that an interface should be able to extend multiple interfaces in a similar way. This feature is demonstrated in Listing 2-47. Listing 2-47. Extending a pair of interfaces interface A { int X = 1; } interface B { double X = 2.0; } interface C extends A, B { } Listing 2-47 will compile even though C inherits two same-named constants X with different return types and initializers. However, if you implement C and then try to access X, as in Listing 2-48, you will run into a name collision. 121 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Listing 2-48. Discovering a name collision class Collision implements C { public void output() { System.out.println(X); // Which X is accessed? } } Suppose you introduce a void foo(); method header declaration into interface A, and an int foo(); method header declaration into interface B. This time, the compiler will report an error when you attempt to compile the modified Listing 2-47. Why Use Interfaces? Now that the mechanics of declaring, implementing, and extending interfaces are out of the way, we can focus on the rationale for using them. Unfortunately, newcomers to Java’s interfaces feature are often told that this feature was created as a workaround to Java’s lack of support for multiple implementation inheritance. While interfaces are useful in this capacity, this is not their reason for existence. Instead, Java’s interfaces feature was created to give developers the utmost flexibility in designing their applications, by decoupling interface from implementation. You should always code to the interface. Those who are adherents to agile software development (a group of software development methodologies based on iterative development that emphasizes keeping code simple, testing frequently, and delivering functional pieces of the application as soon as they are deliverable) know the importance of flexible coding. They cannot afford to tie their code to a specific implementation because a change in requirements for the next iteration could result in a new implementation, and they might find themselves rewriting significant amounts of code, which wastes time and slows development. Interfaces help you achieve flexibility by decoupling interface from implementation. For example, the main() method following Listing 2-36 creates an array of objects from classes that subclass the Shape class, and then iterates over these objects, calling each object’s draw() method. The only objects that can be drawn are those that subclass Shape. Suppose you also have a hierarchy of classes that model resistors, transistors, and other electronic components. Each component has its own symbol that allows the component to be shown in a schematic diagram of an electronic circuit. Perhaps you want to add a drawing capability to each class that draws that component’s symbol. You might consider specifying Shape as the superclass of the electronic component class hierarchy. However, electronic components are not shapes (although they have shapes) so it makes no sense to place these classes in a class hierarchy rooted in Shape. However, you can make each component class implement the Drawable interface, which lets you add expressions that instantiate these classes to the drawables array in the main() method appearing prior to Listing 2-44 (so you can draw their symbols). This is legal because these instances are drawables. Wherever possible, you should strive to specify interfaces instead of classes in your code, to keep your code adaptable to change. This is especially true when working with Java’s Collections Framework, which I will discuss at length in Chapter 5. For now, consider a simple example that consists of the Collections Framework’s java.util.List interface, and its java.util.ArrayList and java.util.LinkedList implementation classes. The following example presents inflexible code based on the ArrayList class: ArrayList<String> arrayList = new ArrayList<String>(); 122 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS void dump(ArrayList<String> arrayList) { // suitable code to dump out the arrayList } This example uses the generics-based parameterized type language feature (which I will discuss in Chapter 3) to identify the kind of objects stored in an ArrayList instance. In this example, String objects are stored. The example is inflexible because it hardwires the ArrayList class into multiple locations. This hardwiring focuses the developer into thinking specifically about array lists instead of generically about lists. Lack of focus is problematic when a requirements change, or perhaps a performance issue brought about by profiling (analyzing a running application to check its performance), suggests that the developer should have used LinkedList. The example only requires a minimal number of changes to satisfy the new requirement. In contrast, a larger code base might need many more changes. Although you only need to change ArrayList to LinkedList, to satisfy the compiler, consider changing arrayList to linkedList, to keep semantics (meaning) clear—you might have to change multiple occurrences of names that refer to an ArrayList instance throughout the source code. The developer is bound to lose time while refactoring the code to adapt to LinkedList. Instead, the developer could have saved time by writing this example to use the equivalent of constants. In other words, the example could have been written to rely on interfaces, and to only specify ArrayList in one place. The following example shows you what the resulting code would look like: List<String> list = new ArrayList<String>(); void dump(List<String> list) { // suitable code to dump out the list } This example is much more flexible than the previous example. If a requirements or profiling change suggests that LinkedList should be used instead of ArrayList, simply replace Array with Linked and you are done. You do not even have to change the parameter name. INTERFACES VERSUS ABSTRACT CLASSES Java provides interfaces and abstract classes for describing abstract types (types that cannot be instantiated). Abstract types represent abstract concepts (drawable and shape, for example), and instances of such types would be meaningless. Interfaces promote flexibility through lack of implementation—Drawable and List illustrate this flexibility. They are not tied to any single class hierarchy, but can be implemented by any class in any hierarchy. Abstract classes support implementation, but can be genuinely abstract (Listing 2-37’s abstract Shape class, for example). However, they are limited to appearing in the upper levels of class hierarchies. Interfaces and abstract classes can be used together. For example, the Collections Framework’s java.util package provides List, Map, and Set interfaces; and AbstractList, AbstractMap, and AbstractSet abstract classes that provide skeletal implementations of these interfaces. 123 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS The skeletal implementations make it easy for you to create your own interface implementations, to address your unique requirements. If they do not meet your needs, you can optionally have your class directly implement the appropriate interface. Collecting Garbage Objects are created via reserved word new, but how are they destroyed? Without some way to destroy objects, they will eventually fill up the heap’s available space and the application will not be able to continue. Java does not provide the developer with the ability to remove them from memory. Instead, Java handles this task by providing a garbage collector, which is code that runs in the background and occasionally checks for unreferenced objects. When the garbage collector discovers an unreferenced object (or multiple objects that reference each other, and where there are no other references to each other—only A references B and only B references A, for example), it removes the object from the heap, making more heap space available. An unreferenced object is an object that cannot be accessed from anywhere within an application. For example, new Employee("John", "Doe"); is an unreferenced object because the Employee reference returned by new is thrown away. In contrast, a referenced object is an object where the application stores at least one reference. For example, Employee emp = new Employee("John", "Doe"); is a referenced object because variable emp contains a reference to the Employee object. A referenced object becomes unreferenced when the application removes its last stored reference. For example, if emp is a local variable that contains the only reference to an Employee object, this object becomes unreferenced when the method in which emp is declared returns. An application can also remove a stored reference by assigning null to its reference variable. For example, emp = null; removes the reference to the Employee object that was previously stored in emp. Java’s garbage collector eliminates a form of memory leakage in C++ implementations that do not rely on a garbage collector. In these C++ implementations, the developer must destroy dynamically created objects before they go out of scope. If they vanish before destruction, they remain in the heap. Eventually, the heap fills and the application halts. Although this form of memory leakage is not a problem in Java, a related form of leakage is problematic: continually creating objects and forgetting to remove even one reference to each object causes the heap to fill up and the application to eventually come to a halt. This form of memory leakage typically occurs in the context of collections (object-based data structures that store objects), and is a major problem for applications that run for lengthy periods of time—a web server is one example. For shorter-lived applications, you will normally not notice this form of memory leakage. Consider Listing 2-49. Listing 2-49. A memory-leaking stack public class Stack { private Object[] elements; private int top; public Stack(int size) { elements = new Object[size]; top = -1; // indicate that stack is empty } public void push(Object o) { 124 n CHAPTER 2 DISCOVERING CLASSES AND OBJECTS if (top+1 == elements.length) { System.out.println("stack is full"); return; } elements[++top] = o; } public Object pop() { if (top == -1) { System.out.println("stack is empty"); return null; } Object element = elements[top--]; // elements[top+1] = null; return element; } public static void main(String[] args) { Stack stack = new Stack(2); stack.push("A"); stack.push("B"); stack.push("C"); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); } } Listing 2-49 describes a collection known as a stack, a data structure that stores elements in last-in, first-out order. Stacks are useful for remembering things, such as the instruction to return to when a method stops executing and must return to its caller. Stack provides a push() method for pushing arbitrary objects onto the top of the stack, and a pop() method for popping objects off the stack’s top in the reverse order to which they were pushed. After creating a Stack object that can store a maximum of two objects, main() invokes push() three times, to push three String objects onto the stack. Because the stack’s internal array can store two objects only, push() outputs an error message when main() tries to push "C". At this point, main() attempts to pop three Objects off of the stack, outputting each object to the standard output device. The first two pop() method calls succeed, but the final method call fails and outputs an error message because the stack is empty when it is called. When you run this application, it generates the following output: stack is full B A stack is empty null There is a problem with the Stack class: it leaks memory. When you push an object onto the stack, its reference is stored in the internal elements array. When you pop an object off the stack, the object’s reference is obtained and top is decremented, but the reference remains in the array (until you invoke push()). 125 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS Imagine a scenario where the Stack object’s reference is assigned to a class field, which means that the Stack object hangs around for the life of the application. Furthermore, suppose that you have pushed three 50-megabyte Image objects onto the stack, and then subsequently popped them off the stack. After using these objects, you assign null to their reference variables, thinking that they will be garbage collected the next time the garbage collector runs. However, this won’t happen because the Stack object still maintains its references to these objects, and so 150 megabytes of heap space will not be available to the application, and maybe the application will run out of memory. The solution to this problem is for pop() to explicitly assign null to the elements entry prior to returning the reference. Simply uncomment the elements[top+1] = null; line in Listing 2-49 to make this happen. You might think that you should always assign null to reference variables when their referenced objects are no longer required. However, doing so often does not improve performance or free up significant amounts of heap space, and can lead to thrown instances of the java.lang.NullPointerException class when you’re not careful. (I discuss NullPointerException in the context of Chapter 3’s coverage of Java’s exceptions-oriented language features). You typically nullify reference variables in classes that manage their own memory, such as the aforementioned Stack class. ■ Note Garbage collection is a complex process and has resulted in various garbage collectors being developed for the JVM. If you want to learn more about garbage collection, I recommend that you start by reading the “Memory Management in the Java HotSpot Virtual Machine” whitepaper at http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf. Next, you will want to learn about the Garbage-First collector, which is new in Java 7. Check out “The GarbageFirst Garbage Collector” whitepaper (http://www.oracle.com/technetwork/java/javase/tech/g1-intro-jsp135488.html) to learn about this garbage collector. For additional information on Java’s garbage collection process, you can explore the other whitepapers that are accessible from Oracle’s “Java HotSpot Garbage Collection” page at http://www.oracle.com/technetwork/java/javase/tech/index-jsp-140228.html. Chapter 4 pursues garbage collection further by introducing you to Java’s Reference API, which lets your application receive notifications when objects are about to be finalized or have been finalized. ■ Note Throughout this book, I often refer to API in both broad and narrow contexts. On the one hand, I refer to Reference as an API, but I also refer to the individual classes of Reference as APIs themselves. 126 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS EXERCISES The following exercises are designed to test your understanding of classes and objects: 1. Listing 2-2 presents an Image class with three constructors and a main() method for testing this class. Expand Image by introducing private int fields named width and height, and a private one-dimensional byte array field named image. Refactor the Image() constructor to invoke the Image(String filename) constructor via this(null). Refactor the Image(String filename, String imageType) constructor such that, when the filename reference is not null, it creates a byte array of arbitrary size, perhaps with the help of an expression such as (int) (Math.random()*100000) (return a randomly generated integer between 0 and 99999 inclusive), and assigns this array’s reference to the image field. Similarly, it assigns an arbitrary width to the width field and an arbitrary height to the height field. If filename contains null, it assigns -1 to each of width and height. Continuing, introduce getWidth(), getHeight(), and getImage() methods that return the values of their respective fields, and introduce a getSize() method that returns the length of the array assigned to the image field (or 0 if image contains the null reference). Finally, refactor the main() method such that, for each constructor, the following sequence of method calls occurs: System.out.println("Image = "+image.getImage()); System.out.println("Size = "+image.getSize()); System.out.println("Width = "+image.getWidth()); System.out.println("Height = "+image.getHeight());. 2. Model part of an animal hierarchy by declaring Animal, Bird, Fish, AmericanRobin, DomesticCanary, RainbowTrout, and SockeyeSalmon classes: • Animal is public and abstract, declares private String-based kind and appearance fields, declares a public constructor that initializes these fields to passed-in arguments, declares public and abstract eat() and move() methods that take no arguments and whose return type is void, and overrides the toString() method to output the contents of kind and appearance. • Bird is public and abstract, extends Animal, declares a public constructor that passes its kind and appearance parameter values to its superclass constructor, overrides its eat() method to output eats seeds and insects (via System.out.println()), and overrides the move() method to output flies through the air. • Fish is public and abstract, extends Animal, declares a public constructor that passes its kind and appearance parameter values to its superclass constructor, overrides its eat() method to output eats krill, algae, and insects, and overrides its move() method to output swims through the water. 127 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS • AmericanRobin is public, extends Bird, and declares a public noargument constructor that passes "americanrobin" and "red breast" to its superclass constructor. • DomesticCanary is public, extends Bird, and declares a public noargument constructor that passes "domesticcanary" and "yellow, orange, black, brown, white, red" to its superclass constructor. • RainbowTrout is public, extends Fish, and declares a public noargument constructor that passes "rainbowtrout" and "bands of brilliant speckled multicolored stripes running nearly the whole length of its body" to its superclass constructor. • SockeyeSalmon is public, extends Fish, and declares a public noargument constructor that passes "sockeyesalmon" and "bright red with a green head" to its superclass constructor. For brevity, I have omitted from the Animal hierarchy abstract Robin, Canary, Trout, and Salmon classes that generalize robins, canaries, trout, and salmon. Perhaps you might want to include these classes in the hierarchy. Although this exercise illustrates the accurate modeling of a natural scenario using inheritance, it also reveals the potential for class explosion—too many classes may be introduced to model a scenario, and it might be difficult to maintain all these classes. Keep this in mind when modeling with inheritance. 3. Continuing from the previous exercise, declare an Animals class with a main() method. This method first declares an animals array that is initialized to AmericanRobin, RainbowTrout, DomesticCanary, and SockeyeSalmon objects. The method then iterates over this array, first outputting animals[i] (which causes toString() to be called), and then calling each object’s eat() and move() methods (demonstrating subtype polymorphism). 4. Continuing from the previous exercise, declare a public Countable interface with a String getID() method. Modify Animal to implement Countable and have this method return kind’s value. Modify Animals to initialize the animals array to AmericanRobin, RainbowTrout, DomesticCanary, SockeyeSalmon, RainbowTrout, and AmericanRobin objects. Also, introduce code that computes a census of each kind of animal. This code will use the Census class that is declared in Listing 2-50. Listing 2-50. The Census class stores census data on four kinds of animals public class Census { public final static int SIZE = 4; private String[] IDs; private int[] counts; public Census() 128 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS { This book was purchased by [email protected] IDs = new String[SIZE]; counts = new int[SIZE]; } public String get(int index) { return IDs[index]+" "+counts[index]; } public void update(String ID) { for (int i = 0; i < IDs.length; i++) { // If ID not already stored in the IDs array (which is indicated by // the first null entry that is found), store ID in this array, and // also assign 1 to the associated element in the counts array, to // initialize the census for that ID. if (IDs[i] == null) { IDs[i] = ID; counts[i] = 1; return; } // If a matching ID is found, increment the associated element in // the counts array to update the census for that ID. if (IDs[i].equals(ID)) { counts[i]++; return; } } } } Summary Structured programs create data structures that organize and store data items, and manipulate the data stored in these data structures via functions and procedures. The fundamental units of a structured program are its data structures and the functions or procedures that manipulate them. Although Java lets you create applications in a similar fashion, this language is really about declaring classes and creating objects from these classes. A class is a template for manufacturing objects (named aggregates of code and data), which are also known as class instances, or instances for short. Classes generalize real-world entities, and objects are specific manifestations of these entities at the program level. Classes model real-world entities from a template perspective. Objects represent specific entities. Entities have attributes. An entity’s collection of attributes is referred to as its state. Entities also have behaviors. A class and its objects model an entity by combining state with behaviors into a single unit—the class abstracts state whereas its objects provide concrete state values. This bringing together of state and behaviors is known as encapsulation. Unlike structured programming, where the developer focuses on 129 CHAPTER 2 DISCOVERING CLASSES AND OBJECTS modeling behaviors through structured code, and modeling state through data structures that store data items for the structured code to manipulate, the developer working with classes and objects focuses on templating entities by declaring classes that encapsulate state and behaviors expressed as fields and methods, instantiating objects with specific field values from these classes to represent specific entities, and interacting with objects by invoking their methods. We tend to categorize stuff by saying things like “cars are vehicles” or “savings accounts are bank accounts.” By making these statements, we really are saying that cars inherit vehicular state (such as make and color) and behaviors (such as park and display mileage), and similarly are saying that savings accounts inherit bank account state (such as balance) and behaviors (such as deposit and withdraw). Car, vehicle, savings account, and bank account are examples of real-world entity categories, and inheritance is a hierarchical relationship between similar entity categories in which one category inherits state and behaviors from at least one other entity category. Inheriting from a single category is called single inheritance, and inheriting from at least two categories is called multiple inheritance. Java supports single inheritance and multiple inheritance to facilitate code reuse—why reinvent the wheel? Java supports single inheritance in a class context, in which a class inherits fields and methods from another class through class extension. Because classes are involved, Java refers to this kind of inheritance as implementation inheritance. Java supports multiple inheritance only in an interface context, in which a class inherits method templates from one or more interfaces through interface implementation, or in which an interface inherits method templates from one or more interfaces through interface extension. Because interfaces are involved, Java refers to this kind of inheritance as interface inheritance. Some real-world entities can change their forms. For example, water is naturally a liquid, but it changes to a solid when frozen, and it changes to a gas when heated to its boiling point. Insects such as butterflies that undergo metamorphosis are another example. The ability to change form is known as polymorphism, and is useful to model in a programming language. For example, code that draws arbitrary shapes can be expressed more concisely by introducing a single Shape class and its draw() method, and by invoking that method for each Circle instance, Rectangle instance, and other Shape instance stored in an array. When Shape’s draw() method is called for an array instance, it is the Circle’s, Rectangle’s or other Shape instance’s draw() method that gets called. We say that there are many forms of Shape’s draw() method, or that this method is polymorphic. Every class X exposes an interface (a protocol consisting of constructors, methods, and [possibly] fields that are made available to objects created from other classes for use in creating and communicating with X’s objects). Java formalizes the interface concept by providing reserved word interface, which is used to introduce a type without implementation. Although many believe that this language feature was created as a workaround to Java’s lack of support for multiple implementation inheritance, this is not the real reason for its existence. Instead, Java’s interfaces feature was created to give developers the utmost flexibility in designing their applications, by decoupling interface from implementation. Objects are created via reserved word new, but how are they destroyed? Without some way to destroy objects, they will eventually fill up the heap’s available space and the application will not be able to continue. Java does not provide the developer with the ability to remove them from memory. Instead, Java handles this task by providing a garbage collector, which is code that runs in the background and occasionally checks for unreferenced objects. When the garbage collector discovers an unreferenced object (or multiple objects that reference each other, and where there are no other references to each other—only A references B and only B references A, for example), it removes the object from the heap, making more heap space available. Now that you understand Java’s support for classes and objects, you’re ready to explore this language’s support for more advanced features such as packages and generics. Chapter 3 introduces you to Java’s support for these and other advanced language features. 130 CHAPTER 3 Exploring Advanced Language Features Chapters 1 and 2 introduced you to Java’s fundamental language features along with its support for classes and objects. Chapter 3 builds onto this foundation by introducing you to Java’s advanced language features, specifically those features related to nested types, packages, static imports, exceptions, assertions, annotations, generics, and enums. Nested Types Classes that are declared outside of any class are known as top-level classes. Java also supports nested classes, which are classes declared as members of other classes or scopes. Nested classes help you implement top-level class architecture. There are four kinds of nested classes: static member classes, nonstatic member classes, anonymous classes, and local classes. The latter three categories are known as inner classes. This section introduces you to static member classes and inner classes. For each kind of nested class, I provide you with a brief introduction, an abstract example, and a more practical example. The section then briefly examines the topic of nesting interfaces within classes. Static Member Classes A static member class is a static member of an enclosing class. Although enclosed, it does not have an enclosing instance of that class, and cannot access the enclosing class’s instance fields and invoke its instance methods. However, it can access the enclosing class’s static fields and invoke its static methods, even those members that are declared private. Listing 3-1 presents a static member class declaration. Listing 3-1. Declaring a static member class class EnclosingClass { private static int i; private static void m1() { System.out.println(i); } static void m2() 131 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES { EnclosedClass.accessEnclosingClass(); } static class EnclosedClass { static void accessEnclosingClass() { i = 1; m1(); } void accessEnclosingClass2() { m2(); } } } Listing 3-1 declares a top-level class named EnclosingClass with class field i, class methods m1() and m2(), and static member class EnclosedClass. Also, EnclosedClass declares class method accessEnclosingClass() and instance method accessEnclosingClass2(). Because accessEnclosingClass() is declared static, m2() must prefix this method’s name with EnclosedClass and the member access operator to invoke this method. Listing 3-2 presents the source code to an application that demonstrates how to invoke EnclosedClass’s accessEnclosingClass() class method, and instantiate EnclosedClass and invoke its accessEnclosingClass2() instance method. Listing 3-2. Invoking a static member class’s class and instance methods class SMCDemo { public static void main(String[] args) { EnclosingClass.EnclosedClass.accessEnclosingClass(); // Output: 1 EnclosingClass.EnclosedClass ec = new EnclosingClass.EnclosedClass(); ec.accessEnclosingClass2(); // Output: 1 } } Listing 3-2’s main() method reveals that you must prefix the name of an enclosed class with the name of its enclosing class to invoke a class method; for example, EnclosingClass.EnclosedClass.accessEnclosingClass();. This listing also reveals that you must prefix the name of the enclosed class with the name of its enclosing class when instantiating the enclosed class; for example, EnclosingClass.EnclosedClass ec = new EnclosingClass.EnclosedClass();. You can then invoke the instance method in the normal manner; for example, ec.accessEnclosingClass2();. Static member classes have their uses. For example, Listing 3-3’s Double and Float static member classes provide different implementations of their enclosing Rectangle class. The Float version occupies less memory because of its 32-bit float fields, and the Double version provides greater accuracy because of its 64-bit double fields. 132 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-3. Using static member classes to declare multiple implementations of their enclosing class abstract class Rectangle { abstract double getX(); abstract double getY(); abstract double getWidth(); abstract double getHeight(); static class Double extends Rectangle { private double x, y, width, height; Double(double x, double y, double width, double height) { this.x = x; this.y = y; this.width = width; this.height = height; } double getX() { return x; } double getY() { return y; } double getWidth() { return width; } double getHeight() { return height; } } static class Float extends Rectangle { private float x, y, width, height; Float(float x, float y, float width, float height) { this.x = x; this.y = y; this.width = width; this.height = height; } double getX() { return x; } double getY() { return y; } double getWidth() { return width; } double getHeight() { return height; } } // Prevent subclassing. Use the type-specific Double and Float // implementation subclass classes to instantiate. private Rectangle() {} boolean contains(double x, double y) { return (x >= getX() && x < getX()+getWidth()) && (y >= getY() && y < getY()+getHeight()); } } Listing 3-3’s Rectangle class demonstrates nested subclasses. Each of the Double and Float static member classes subclass the abstract Rectangle class, providing private floating-point or double 133 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES precision floating-point fields, and overriding Rectangle’s abstract methods to return these fields’ values as doubles. Rectangle is abstract because it makes no sense to instantiate this class. Because it also makes no sense to directly extend Rectangle with new implementations (the Double and Float nested subclasses should be sufficient), its default constructor is declared private. Instead, you must instantiate Rectangle.Float (to save memory) or Rectangle.Double (when accuracy is required), as demonstrated by Listing 3-4. Listing 3-4. Instantiating nested subclasses class SMCDemo { public static void main(String[] args) { Rectangle r = new Rectangle.Double(10.0, 10.0, 20.0, 30.0); System.out.println("x = "+r.getX()); System.out.println("y = "+r.getY()); System.out.println("width = "+r.getWidth()); System.out.println("height = "+r.getHeight()); System.out.println("contains(15.0, 15.0) = "+r.contains(15.0, 15.0)); System.out.println("contains(0.0, 0.0) = "+r.contains(0.0, 0.0)); System.out.println(); r = new Rectangle.Float(10.0f, 10.0f, 20.0f, 30.0f); System.out.println("x = "+r.getX()); System.out.println("y = "+r.getY()); System.out.println("width = "+r.getWidth()); System.out.println("height = "+r.getHeight()); System.out.println("contains(15.0, 15.0) = "+r.contains(15.0, 15.0)); System.out.println("contains(0.0, 0.0) = "+r.contains(0.0, 0.0)); } } Listing 3-4 first instantiates Rectangle’s Double subclass via new Rectangle.Double(10.0, 10.0, 20.0, 30.0) and then invokes its various methods. Continuing, Listing 3-4 instantiates Rectangle’s Float subclass via new Rectangle.Float(10.0f, 10.0f, 20.0f, 30.0f) before invoking Rectangle methods on this instance. Compile both listings (javac SMCDemo.java or javac *.java) and run the application (java SMCDemo). You will then observe the following output: x = 10.0 y = 10.0 width = 20.0 height = 30.0 contains(15.0, 15.0) = true contains(0.0, 0.0) = false x = 10.0 y = 10.0 width = 20.0 height = 30.0 contains(15.0, 15.0) = true contains(0.0, 0.0) = false 134 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Java’s class library contains many static member classes. For example, the java.lang.Character class encloses a static member class named Subset whose instances represent subsets of the Unicode character set. java.util.AbstractMap.SimpleEntry, java.io.ObjectInputStream.GetField, and java.security.KeyStore.PrivateKeyEntry are other examples. ■ Note When you compile an enclosing class that contains a static member class, the compiler creates a classfile for the static member class whose name consists of its enclosing class’s name, a dollar-sign character, and the static member class’s name. For example, compile Listing 3-1 and you will discover EnclosingClass$EnclosedClass.class as well as EnclosingClass.class. This format also applies to nonstatic member classes. Nonstatic Member Classes A nonstatic member class is a non-static member of an enclosing class. Each instance of the nonstatic member class implicitly associates with an instance of the enclosing class. The nonstatic member class’s instance methods can call instance methods in the enclosing class and access the enclosing class instance’s nonstatic fields. Listing 3-5 presents a nonstatic member class declaration. Listing 3-5. Declaring a nonstatic member class class EnclosingClass { private int i; private void m() { System.out.println(i); } class EnclosedClass { void accessEnclosingClass() { i = 1; m(); } } } Listing 3-5 declares a top-level class named EnclosingClass with instance field i, instance method m(), and nonstatic member class EnclosedClass. Furthermore, EnclosedClass declares instance method accessEnclosingClass(). Because accessEnclosingClass() is nonstatic, EnclosedClass must be instantiated before this method can be called. This instantiation must take place via an instance of EnclosingClass. Listing 3-6 accomplishes these tasks. 135 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-6. Calling a nonstatic member class’s instance method class NSMCDemo { public static void main(String[] args) { EnclosingClass ec = new EnclosingClass(); ec.new EnclosedClass().accessEnclosingClass(); // Output: 1 } } Listing 3-6’s main() method first instantiates EnclosingClass and saves its reference in local variable ec. Then, main() uses this reference as a prefix to the new operator, to instantiate EnclosedClass, whose reference is then used to call accessEnclosingClass(), which outputs 1. ■ Note Prefixing new with a reference to the enclosing class is rare. Instead, you will typically call an enclosed class’s constructor from within a constructor or an instance method of its enclosing class. Suppose you need to maintain a to-do list of items, where each item consists of a name and a description. After some thought, you create Listing 3-7’s ToDo class to implement these items. Listing 3-7. Implementing to-do items as name-description pairs class ToDo { private String name; private String desc; ToDo(String name, String desc) { this.name = name; this.desc = desc; } String getName() { return name; } String getDesc() { return desc; } @Override public String toString() { return "Name = "+getName()+", Desc = "+getDesc(); } } 136 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES You next create a ToDoList class to store ToDo instances. ToDoList uses its ToDoArray nonstatic member class to store ToDo instances in a growable array – you do not know how many instances will be stored, and Java arrays have fixed lengths. See Listing 3-8. Listing 3-8. Storing a maximum of two ToDo instances in a ToDoArray instance class ToDoList { private ToDoArray toDoArray; private int index = 0; ToDoList() { toDoArray = new ToDoArray(2); } boolean hasMoreElements() { return index < toDoArray.size(); } ToDo nextElement() { return toDoArray.get(index++); } void add(ToDo item) { toDoArray.add(item); } private class ToDoArray { private ToDo[] toDoArray; private int index = 0; ToDoArray(int initSize) { toDoArray = new ToDo[initSize]; } void add(ToDo item) { if (index >= toDoArray.length) { ToDo[] temp = new ToDo[toDoArray.length*2]; for (int i = 0; i < toDoArray.length; i++) temp[i] = toDoArray[i]; toDoArray = temp; } toDoArray[index++] = item; } ToDo get(int i) { return toDoArray[i]; } int size() { return index; 137 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES } } } As well as providing an add() method to store ToDo instances in the ToDoArray instance, ToDoList provides hasMoreElements() and nextElement() methods to iterate over and return the stored instances. Listing 3-9 demonstrates these methods. Listing 3-9. Creating and iterating over a ToDoList of ToDo instances class NSMCDemo { public static void main(String[] args) { ToDoList toDoList = new ToDoList(); toDoList.add(new ToDo("#1", "Do laundry.")); toDoList.add(new ToDo("#2", "Buy groceries.")); toDoList.add(new ToDo("#3", "Vacuum apartment.")); toDoList.add(new ToDo("#4", "Write report.")); toDoList.add(new ToDo("#5", "Wash car.")); while (toDoList.hasMoreElements()) System.out.println(toDoList.nextElement()); } } Compile all three listings (javac NSMCDemo.java or javac *.java) and run the application (java NSMCDemo). You will then observe the following output: Name Name Name Name Name = = = = = #1, #2, #3, #4, #5, Desc Desc Desc Desc Desc = = = = = Do laundry. Buy groceries. Vacuum apartment. Write report. Wash car. Java’s class library presents many examples of nonstatic member classes. For example, the java.util package’s HashMap class declares private HashIterator, ValueIterator, KeyIterator, and EntryIterator classes for iterating over a hashmap’s values, keys, and entries. (I will discuss HashMap in Chapter 5.) ■ Note Code within an enclosed class can obtain a reference to its enclosing class instance by qualifying reserved word this with the enclosing class’s name and the member access operator. For example, if code within accessEnclosingClass() needed to obtain a reference to its EnclosingClass instance, it would specify EnclosingClass.this. Anonymous Classes An anonymous class is a class without a name. Furthermore, it is not a member of its enclosing class. Instead, an anonymous class is simultaneously declared (as an anonymous extension of a class or as an 138 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES anonymous implementation of an interface) and instantiated any place where it is legal to specify an expression. Listing 3-10 demonstrates an anonymous class declaration and instantiation. This book was purchased by [email protected] Listing 3-10. Declaring and instantiating an anonymous class that extends a class abstract class Speaker { abstract void speak(); } class ACDemo { public static void main(final String[] args) { new Speaker() { String msg = (args.length == 1) ? args[0] : "nothing to say"; @Override void speak() { System.out.println(msg); } } .speak(); } } Listing 3-10 introduces an abstract class named Speaker and a concrete class named ACDemo. The latter class’s main() method declares an anonymous class that extends Speaker and overrides its speak() method. When this method is called, it outputs main()’s first command-line argument or a default message if there are no arguments; for example, java ACDemo Hello outputs Hello. An anonymous class does not have a constructor (because the anonymous class does not have a name). However, its classfile does contain an <init>() method that performs instance initialization. This method calls the superclass’s noargument constructor (prior to any other initialization), which is the reason for specifying Speaker() after new. Anonymous class instances should be able to access the surrounding scope’s local variables and parameters. However, an instance might outlive the method in which it was conceived (as a result of storing the instance’s reference in a field), and try to access local variables and parameters that no longer exist after the method returns. Because Java cannot allow this illegal access, which would most likely crash the Java Virtual Machine (JVM), it lets an anonymous class instance only access local variables and parameters that are declared final. Upon encountering a final local variable/parameter name in an anonymous class instance, the compiler does one of two things: • If the variable’s type is primitive (int or double, for example), the compiler replaces its name with the variable’s read-only value. • If the variable’s type is reference (java.lang.String, for example), the compiler introduces, into the classfile, a synthetic variable (a manufactured variable) and code that stores the local variable’s/parameter’s reference in the synthetic variable. Listing 3-11 demonstrates an alternative anonymous class declaration and instantiation. 139 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-11. Declaring and instantiating an anonymous class that implements an interface interface Speakable { void speak(); } class ACDemo { public static void main(final String[] args) { new Speakable() { String msg = (args.length == 1) ? args[0] : "nothing to say"; @Override public void speak() { System.out.println(msg); } } .speak(); } } Listing 3-11 is very similar to Listing 3-10. However, instead of subclassing a Speaker class, this listing’s anonymous class implements an interface named Speakable. Apart from the <init>() method calling java.lang.Object() (interfaces have no constructors), Listing 3-11 behaves like Listing 3-10. Although an anonymous class does not have a constructor, you can provide an instance initializer to handle complex initialization. For example, new Office() {{addEmployee(new Employee("John Doe"));}}; instantiates an anonymous subclass of Office and adds one Employee object to this instance by calling Office’s addEmployee() method. You will often find yourself creating and instantiating anonymous classes for their convenience. For example, suppose you need to return a list of all filenames having the “.java” suffix. The following example shows you how an anonymous class simplifies using the java.io package’s File and FilenameFilter classes to achieve this objective: String[] list = new File(directory).list(new FilenameFilter() { @Override public boolean accept(File f, String s) { return s.endsWith(".java"); } }); Local Classes A local class is a class that is declared anywhere that a local variable is declared. Furthermore, it has the same scope as a local variable. Unlike an anonymous class, a local class has a name and can be reused. Like anonymous classes, local classes only have enclosing instances when used in nonstatic contexts. 140 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES A local class instance can access the surrounding scope’s local variables and parameters. However, the local variables and parameters that are accessed must be declared final. For example, Listing 3-12’s local class declaration accesses a final parameter and a final local variable. Listing 3-12. Declaring a local class class EnclosingClass { void m(final int x) { final int y = x*2; class LocalClass { int a = x; int b = y; } LocalClass lc = new LocalClass(); System.out.println(lc.a); System.out.println(lc.b); } } Listing 3-12 declares EnclosingClass with its instance method m() declaring a local class named LocalClass. This local class declares a pair of instance fields (a and b) that are initialized to the values of final parameter x and final local variable y when LocalClass is instantiated: new EnclosingClass().m(10);, for example. Listing 3-13 demonstrates this local class. Listing 3-13. Demonstrating a local class class LCDemo { public static void main(String[] args) { EnclosingClass ec = new EnclosingClass(); ec.m(10); } } After instantiating EnclosingClass, Listing 3-13’s main() method invokes m(10). The called m() method multiplies this argument by 2, instantiates LocalClass, whose <init>() method assigns the argument and the doubled value to its pair of instance fields (in lieu of using a constructor to perform this task), and outputs the LocalClass instance fields. The following output results: 10 20 Local classes help improve code clarity because they can be moved closer to where they are needed. For example, Listing 3-14 declares an Iterator interface and a refactored ToDoList class whose iterator() method returns an instance of its local Iter class as an Iterator instance (because Iter implements Iterator). 141 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-14. The Iterator interface and the refactored ToDoList class interface Iterator { boolean hasMoreElements(); Object nextElement(); } class ToDoList { private ToDo[] toDoList; private int index = 0; ToDoList(int size) { toDoList = new ToDo[size]; } Iterator iterator() { class Iter implements Iterator { int index = 0; @Override public boolean hasMoreElements() { return index < toDoList.length; } @Override public Object nextElement() { return toDoList[index++]; } } return new Iter(); } void add(ToDo item) { toDoList[index++] = item; } } Listing 3-15 demonstrates Iterator, the refactored ToDoList class, and Listing 3-7’s ToDo class. Listing 3-15. Creating and iterating over a ToDoList of ToDo instances with a reusable iterator class LCDemo { public static void main(String[] args) { ToDoList toDoList = new ToDoList(5); toDoList.add(new ToDo("#1", "Do laundry.")); toDoList.add(new ToDo("#2", "Buy groceries.")); toDoList.add(new ToDo("#3", "Vacuum apartment.")); 142 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES toDoList.add(new ToDo("#4", "Write report.")); toDoList.add(new ToDo("#5", "Wash car.")); Iterator iter = toDoList.iterator(); while (iter.hasMoreElements()) System.out.println(iter.nextElement()); } } The Iterator instance that is returned from iterator() returns ToDo items in the same order as when they were added to the list. Although you can only use the returned Iterator object once, you can call iterator() whenever you need a new Iterator object. This capability is a big improvement over the one-shot iterator presented in Listing 3-9. Interfaces Within Classes Interfaces can be nested within classes. Once declared, an interface is considered to be static, even if it is not declared static. For example, Listing 3-16 declares an enclosing class named X along with two nested static interfaces named A and B. Listing 3-16. Declaring a pair of interfaces within a class class X { interface A { } static interface B { } } You would access Listing 3-16’s interfaces in the same way. For example, you would specify class C implements X.A {} or class D implements X.B {}. As with nested classes, nested interfaces help to implement top-level class architecture by being implemented via nested classes. Collectively, these types are nested because they cannot (as in Listing 314’s Iter local class) or need not appear at the same level as a top-level class and pollute its package namespace. ■ Note Chapter 2’s introduction to interfaces showed you how to declare constants and method headers in the body of an interface. You can also declare interfaces and classes in an interface’s body. Because there are few good reasons to do this (java.util.Map.Entry, which is discussed in Chapter 5, is one exception), it is probably best to avoid nesting interfaces and/or classes within interfaces. 143 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Packages Hierarchical structures organize items in terms of hierarchical relationships that exist between those items. For example, a filesystem might contain a taxes directory with multiple year subdirectories, where each subdirectory contains tax information pertinent to that year. Also, an enclosing class might contain multiple nested classes that only make sense in the context of the enclosing class. Hierarchical structures also help to avoid name conflicts. For example, two files cannot have the same name in a nonhierarchical filesystem (which consists of a single directory). In contrast, a hierarchical filesystem lets same-named files exist in different directories. Similarly, two enclosing classes can contain same-named nested classes. Name conflicts do not exist because items are partitioned into different namespaces. Java also supports the partitioning of top-level user-defined types into multiple namespaces, to better organize these types and to also prevent name conflicts. Java uses packages to accomplish these tasks. This section introduces you to packages. After defining this term and explaining why package names must be unique, the section presents the package and import statements. It next explains how the JVM searches for packages and types, and then presents an example that shows you how to work with packages. This section closes by showing you how to encapsulate a package of classfiles into JAR files. ■ Tip Except for the most trivial of top-level types and (typically) those classes that serve as application entry points, you should consider storing your types (especially if they are reusable) in packages. What Are Packages? A package is a unique namespace that can contain a combination of top-level classes, other top-level types, and subpackages. Only types that are declared public can be accessed from outside the package. Furthermore, the constants, constructors, methods, and nested types that describe a class’s interface must be declared public to be accessible from beyond the package. ■ Note Throughout this book, I typically don’t declare top-level types and their accessible members public, unless I’m creating a package. Every package has a name, which must be a nonreserved identifier. The member access operator separates a package name from a subpackage name, and separates a package or subpackage name from a type name. For example, the two member access operators in graphics.shapes.Circle separate package name graphics from the shapes subpackage name, and separate subpackage name shapes from the Circle type name. 144 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES ■ Note The standard class library organizes its many classes and other top-level types into multiple packages. Many of these packages are subpackages of the standard java package. Examples include java.io (types related to input/output operations), java.lang (language-oriented types), java.lang.reflect (reflection-oriented language types), java.net (network-oriented types), and java.util (utility types). Package Names Must Be Unique Suppose you have two different graphics.shapes packages, and suppose that each shapes subpackage contains a Circle class with a different interface. When the compiler encounters System.out.println(new Circle(10.0, 20.0, 30.0).area()); in the source code, it needs to verify that the area() method exists. The compiler will search all accessible packages until it finds a graphics.shapes package that contains a Circle class. If the found package contains the appropriate Circle class with an area() method, everything is fine; otherwise, if the Circle class does not have an area() method, the compiler will report an error. This scenario illustrates the importance of choosing unique package names. Specifically, the toplevel package name must be unique. The convention in choosing this name is to take your Internet domain name and reverse it. For example, I would choose ca.tutortutor as my top-level package name because tutortutor.ca is my domain name. I would then specify ca.tutortutor.graphics.shapes.Circle to access Circle. ■ Note Reversed Internet domain names are not always valid package names. One or more of its component names might start with a digit (6.com), contain a hyphen (-) or other illegal character (aq-x.com), or be one of Java’s reserved words (int.com). Convention dictates that you prefix the digit with an underscore (com._6), replace the illegal character with an underscore (com.aq_x), and suffix the reserved word with an underscore (com.int_). The Package Statement The package statement identifies the package in which a source file’s types are located. This statement consists of reserved word package, followed by a member access operator-separated list of package and subpackage names, followed by a semicolon. For example, package graphics; specifies that the source file’s types locate in a package named graphics, and package graphics.shapes; specifies that the source file’s types locate in the graphics package’s shapes subpackage. By convention, a package name is expressed in lowercase. If the name consists of multiple words, each word except for the first word is capitalized. Only one package statement can appear in a source file. When it is present, nothing apart from comments must precede this statement. 145 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES ■ Caution Specifying multiple package statements in a source file or placing anything apart from comments above a package statement causes the compiler to report an error. Java implementations map package and subpackage names to same-named directories. For example, an implementation would map graphics to a directory named graphics, and would map graphics.shapes to a shapes subdirectory of graphics. The Java compiler stores the classfiles that implement the package’s types in the corresponding directory. ■ Note If a source file does not contain a package statement, the source file’s types are said to belong to the unnamed package. This package corresponds to the current directory. The Import Statement Imagine having to repeatedly specify ca.tutortutor.graphics.shapes.Circle or some other lengthy package-qualified type name for each occurrence of that type in source code. Java provides an alternative that lets you avoid having to specify package details. This alternative is the import statement. The import statement imports types from a package by telling the compiler where to look for unqualified type names during compilation. This statement consists of reserved word import, followed by a member access operator-separated list of package and subpackage names, followed by a type name or * (asterisk), followed by a semicolon. The * symbol is a wildcard that represents all unqualified type names. It tells the compiler to look for such names in the import statement’s specified package, unless the type name is found in a previously searched package. (Using the wildcard does not have a performance penalty or lead to code bloat, but can lead to name conflicts, as you will see.) For example, import ca.tutortutor.graphics.shapes.Circle; tells the compiler that an unqualified Circle class exists in the ca.tutortutor.graphics.shapes package. Similarly, import ca.tutortutor.graphics.shapes.*; tells the compiler to look in this package if it encounters a Rectangle class, a Triangle class, or even an Employee class (if Employee has not already been found). ■ Tip You should avoid using the * wildcard so that other developers can easily see which types are used in source code. Because Java is case sensitive, package and subpackage names specified in an import statement must be expressed in the same case as that used in the package statement. When import statements are present in source code, only a package statement and comments can precede them. 146 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES ■ Caution Placing anything other than a package statement, import statements, static import statements (discussed shortly), and comments above an import statement causes the compiler to report an error. You can run into name conflicts when using the wildcard version of the import statement because any unqualified type name matches the wildcard. For example, you have graphics.shapes and geometry packages that each contain a Circle class, the source code begins with import geometry.*; and import graphics.shape.*; statements, and it also contains an unqualified occurrence of Circle. Because the compiler does not know if Circle refers to geometry’s Circle class or graphics.shape’s Circle class, it reports an error. You can fix this problem by qualifying Circle with the correct package name. ■ Note The compiler automatically imports the String class and other types from the java.lang package, which is why it is not necessary to qualify String with java.lang. Searching for Packages and Types Newcomers to Java who first start to work with packages often become frustrated by “no class definition found” and other errors. This frustration can be partly avoided by understanding how the JVM searches for packages and types. This section explains how the search process works. To understand this process, you need to realize that the compiler is a special Java application that runs under the control of the JVM. Furthermore, there are two different forms of search. Compile-Time Search When the compiler encounters a type expression (such as a method call) in source code, it must locate that type’s declaration to verify that the expression is legal (a method exists in the type’s class whose parameter types match the types of the arguments passed in the method call, for example). The compiler first searches the Java platform packages (which contain class library types). It then searches extension packages (for extension types). If the -sourcepath command-line option was specified when starting the JVM (via javac), the compiler searches the indicated path’s source files. ■ Note Java platform packages are stored in rt.jar and a few other important JAR files. Extension packages are stored in a special extensions directory named ext. Otherwise, the compiler searches the user classpath (in left-to-right order) for the first user classfile or source file containing the type. If no user classpath is present, the current directory is searched. If no 147 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES package matches or the type still cannot be found, the compiler reports an error. Otherwise, the compiler records the package information in the classfile. ■ Note The user classpath is specified via the -classpath option used to start the JVM or, if not present, the CLASSPATH environment variable. Runtime Search When the compiler or any other Java application runs, the JVM will encounter types and must load their associated classfiles via special code known as a classloader (discussed in Appendix C). The JVM will use the previously stored package information that is associated with the encountered type in a search for that type’s classfile. The JVM searches the Java platform packages, followed by extension packages, followed by the user classpath (in left-to-right order) for the first classfile that contains the type. If no user classpath is present, the current directory is searched. If no package matches or the type cannot be found, a “no class definition found” error is reported. Otherwise, the classfile is loaded into memory. ■ Note Whether you use the -classpath option or the CLASSPATH environment variable to specify a user classpath, there is a specific format that must be followed. Under Windows, this format is expressed as path1;path2;..., where path1, path2, and so on are the locations of package directories. Under Unix and Linux, this format changes to path1:path2:.... Playing with Packages Suppose your application needs to log messages to the console, to a file, or to another destination. It can accomplish this task with the help of a logging library. My implementation of this library consists of an interface named Logger, an abstract class named LoggerFactory, and a pair of package-private classes named Console and File. ■ Note The logging library that I present is an example of the Abstract Factory design pattern, which is presented on page 87 of Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1995; ISBN: 0201633612). Listing 3-17 presents the Logger interface, which describes objects that log messages. 148 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-17. Describing objects that log messages via the Logger interface package logging; public interface Logger { boolean connect(); boolean disconnect(); boolean log(String msg); } Each of the connect(), disconnect(), and log() methods returns true upon success, and false upon failure. (Later in this chapter, you will discover a better technique for dealing with failure.) These methods are not declared public explicitly because an interface’s methods are implicitly public. Listing 3-18 presents the LoggerFactory abstract class. This book was purchased by [email protected] Listing 3-18. Obtaining a logger for logging messages to a specific destination package logging; public abstract class LoggerFactory { public final static int CONSOLE = 0; public final static int FILE = 1; public static Logger newLogger(int dstType, String... dstName) { switch (dstType) { case CONSOLE: return new Console(dstName.length == 0 ? null : dstName[0]); case FILE : return new File(dstName.length == 0 ? null : dstName[0]); default : return null; } } } newLogger() returns a Logger instance for logging messages to an appropriate destination. It uses the variable number of arguments feature (see Chapter 2) to optionally accept an extra String argument for those destination types that require the argument. For example, FILE requires a filename. Listing 3-19 presents the package-private Console class – this class is not accessible beyond the classes in the logging package because reserved word class is not preceded by reserved word public. Listing 3-19. Logging messages to the console package logging; class Console implements Logger { private String dstName; Console(String dstName) 149 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES { this.dstName = dstName; } @Override public boolean connect() { return true; } @Override public boolean disconnect() { return true; } @Override public boolean log(String msg) { System.out.println(msg); return true; } } Console’s package-private constructor saves its argument, which most likely will be null because there is no need for a String argument. Perhaps a future version of Console will use this argument to identify one of multiple console windows. Listing 3-20 presents the package-private File class. Listing 3-20. Logging messages to a file (eventually) package logging; class File implements Logger { private String dstName; File(String dstName) { this.dstName = dstName; } @Override public boolean connect() { if (dstName == null) return false; System.out.println("opening file "+dstName); return true; } @Override public boolean disconnect() { if (dstName == null) return false; System.out.println("closing file "+dstName); return true; 150 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES } @Override public boolean log(String msg) { if (dstName == null) return false; System.out.println("writing "+msg+" to file "+dstName); return true; } } Unlike Console, File requires a nonnull argument. Each method first verifies that this argument is not null. If the argument is null, the method returns false to signify failure. (In Chapter 8, I refactor File to incorporate appropriate file-writing code.) The logging library allows us to introduce portable logging code into an application. Apart from a call to newLogger(), this code will remain the same regardless of the logging destination. Listing 3-21 presents an application that tests this library. Listing 3-21. Testing the logging library import logging.Logger; import logging.LoggerFactory; class TestLogger { public static void main(String[] args) { Logger logger = LoggerFactory.newLogger(LoggerFactory.CONSOLE); if (logger.connect()) { logger.log("test message #1"); logger.disconnect(); } else System.out.println("cannot connect to console-based logger"); logger = LoggerFactory.newLogger(LoggerFactory.FILE, "x.txt"); if (logger.connect()) { logger.log("test message #2"); logger.disconnect(); } else System.out.println("cannot connect to file-based logger"); logger = LoggerFactory.newLogger(LoggerFactory.FILE); if (logger.connect()) { logger.log("test message #3"); logger.disconnect(); } else System.out.println("cannot connect to file-based logger"); } 151 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES } Follow these steps (which assume that the JDK has been installed) to create the logging package and TestLogger application, and to run this application: 1. Create a new directory and make this directory current. 2. Create a logging directory in the current directory. 3. Copy Listing 3-17 to a file named Logger.java in the logging directory. 4. Copy Listing 3-18 to a file named LoggerFactory.java in the logging directory. 5. Copy Listing 3-19 to a file named Console.java in the logging directory. 6. Copy Listing 3-20 to a file named File.java in the logging directory. 7. Copy Listing 3-21 to a file named TestLogger.java in the current directory. 8. Execute javac TestLogger.java, which also compiles logger’s source files. 9. Execute java TestLogger. After completing the final step, you should observe the following output from the TestLogger application: test message #1 opening file x.txt writing test message #2 to file x.txt closing file x.txt cannot connect to file-based logger What happens when logging is moved to another location? For example, move logging to the root directory and run TestLogger. You will now observe an error message about the JVM not finding the logging package and its LoggerFactory classfile. You can solve this problem by specifying -classpath when running the java tool, or by adding the location of the logging package to the CLASSPATH environment variable. You’ll probably find it more convenient to use the former option, as demonstrated in the following Windows-specific command line: java -classpath \;. TestLogger The backslash represents the root directory in Windows. (I could have specified a forward slash as an alternative.) Also, the period represents the current directory. If it is missing, the JVM complains about not finding the TestLogger classfile. ■ Tip If you discover an error message where the JVM reports that it cannot find an application classfile, try appending a period character to the classpath. Doing so will probably fix the problem. 152 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Packages and JAR Files Chapter 1 briefly introduced you to the JDK’s jar tool, which is used to archive classfiles in JAR files, and is also used to extract a JAR file’s classfiles. It probably comes as no surprise that you can store packages in JAR files, which greatly simplify the distribution of your package-based class libraries. To show you how easy it is to store a package in a JAR file, we will create a logger.jar file that contains the logging package’s four classfiles (Logger.class, LoggerFactory.class, Console.class, and File.class). Complete the following steps to accomplish this task: 1. Make sure that the current directory contains the previously created logging directory with its four classfiles. 2. Execute jar cf logger.jar logging\*.class. You could alternatively execute jar cf logger.jar logging/*.class. (The c option stands for “create new archive” and the f option stands for “specify archive filename.”) You should now find a logger.jar file in the current directory. To prove to yourself that this file contains the four classfiles, execute jar tf logger.jar. (The t option stands for “list table of contents.”) You can run TestLogger.class by adding logger.jar to the classpath. For example, you can run TestLogger under Windows via java -classpath logger.jar;. TestLogger. Static Imports An interface should only be used to declare a type. However, some developers violate this principle by using interfaces to only export constants. Such interfaces are known as constant interfaces, and Listing 322 presents an example. Listing 3-22. Declaring a constant interface interface Directions { int NORTH = 0; int SOUTH = 1; int EAST = 2; int WEST = 3; } Developers who resort to constant interfaces do so to avoid having to prefix a constant’s name with the name of its class (as in Math.PI, where PI is a constant in the java.lang.Math class). They do this by implementing the interface—see Listing 3-23. Listing 3-23. Implementing a constant interface class TrafficFlow implements Directions { public static void main(String[] args) { showDirection((int)(Math.random()*4)); } static void showDirection(int dir) { switch (dir) 153 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES { case case case case NORTH: SOUTH: EAST : WEST : System.out.println("Moving System.out.println("Moving System.out.println("Moving System.out.println("Moving north"); break; south"); break; east"); break; west"); } } } Listing 3-23’s TrafficFlow class implements Directions for the sole purpose of not having to specify Directions.NORTH, Directions.SOUTH, Directions.EAST, and Directions.WEST. This is an appalling misuse of an interface. These constants are nothing more than an implementation detail that should not be allowed to leak into the class’s exported interface, because they might confuse the class’s users (what is the purpose of these constants?). Also, they represent a future commitment: even when the class no longer uses these constants, the interface must remain to ensure binary compatibility. Java 5 introduced an alternative that satisfies the desire for constant interfaces while avoiding their problems. This static imports feature lets you import a class’s static members so that you do not have to qualify them with their class names. It is implemented via a small modification to the import statement, as follows: import static packagespec . classname . ( staticmembername | * ); The static import statement specifies static after import. It then specifies a member access operator-separated list of package and subpackage names, which is followed by the member access operator and a class’s name. Once again, the member access operator is specified, followed by a single static member name or the asterisk wildcard. ■ Caution Placing anything apart from a package statement, import/static import statements, and comments above a static import statement causes the compiler to report an error. You specify a single static member name to import only that name: import static java.lang.Math.PI; // Import the PI static field only. import static java.lang.Math.cos; // Import the cos() static method only. In contrast, you specify the wildcard to import all static member names: import static java.lang.Math.*; // Import all static members from Math. You can now refer to the static member(s) without having to specify the class name: System.out.println(cos(PI)); Using multiple static import statements can result in name conflicts, which causes the compiler to report errors. For example, suppose your geom package contains a Circle class with a static member named PI. Now suppose you specify import static java.lang.Math.*; and import static geom.Circle.*; at the top of your source file. Finally, suppose you specify System.out.println(PI); somewhere in that file’s code. The compiler reports an error because it does not know if PI belongs to Math or Circle. 154 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Exceptions In an ideal world, nothing bad ever happens when an application runs. For example, a file always exists when the application needs to open the file, the application is always able to connect to a remote computer, and the JVM never runs out of memory when the application needs to instantiate objects. In contrast, real-world applications occasionally attempt to open files that do not exist, attempt to connect to remote computers that are unable to communicate with them, and require more memory than the JVM can provide. Your goal is to write code that properly responds to these and other exceptional situations (exceptions). This section introduces you to exceptions. After defining this term, the section looks at representing exceptions in source code. It then examines the topics of throwing and handling exceptions, and concludes by discussing how to perform cleanup tasks before a method returns, whether or not an exception has been thrown. What Are Exceptions? An exception is a divergence from an application’s normal behavior. For example, the application attempts to open a nonexistent file for reading. The normal behavior is to successfully open the file and begin reading its contents. However, the file cannot be read if the file does not exist. This example illustrates an exception that cannot be prevented. However, a workaround is possible. For example, the application can detect that the file does not exist and take an alternate course of action, which might include telling the user about the problem. Unpreventable exceptions where workarounds are possible must not be ignored. Exceptions can occur because of poorly written code. For example, an application might contain code that accesses each element in an array. Because of careless oversight, the array-access code might attempt to access a nonexistent array element, which leads to an exception. This kind of exception is preventable by writing correct code. Finally, an exception might occur that cannot be prevented, and for which there is no workaround. For example, the JVM might run out of memory, or perhaps it cannot find a classfile. This kind of exception, known as an error, is so serious that it is impossible (or at least inadvisable) to work around; the application must terminate, presenting a message to the user that explains why it is terminating. Representing Exceptions in Source Code An exception can be represented via error codes or objects. After discussing each kind of representation and explaining why objects are superior, I introduce you to Java’s exception and error class hierarchy, emphasizing the difference between checked and runtime exceptions. I close my discussion on representing exceptions in source code by discussing custom exception classes. Error Codes Versus Objects One way to represent exceptions in source code is to use error codes. For example, a method might return true on success and false when an exception occurs. Alternatively, a method might return 0 on success and a nonzero integer value that identifies a specific kind of exception. Developers traditionally designed methods to return error codes; I demonstrated this tradition in each of the three methods in Listing 3-17’s Logger interface. Each method returns true on success, or returns false to represent an exception (unable to connect to the logger, for example). Although a method’s return value must be examined to see if it represents an exception, error codes are all too easy to ignore. For example, a lazy developer might ignore the return code from Logger’s 155 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES connect() method and attempt to call log(). Ignoring error codes is one reason why a new approach to dealing with exceptions has been invented. This new approach is based on objects. When an exception occurs, an object representing the exception is created by the code that was running when the exception occurred. Details describing the exception’s surrounding context are stored in the object. These details are later examined to work around the exception. The object is then thrown, or handed off to the JVM to search for a handler, code that can handle the exception. (If the exception is an error, the application should not provide a handler because errors are so serious [e.g., the JVM has run out of memory] that there’s practically nothing that can be done about them.) When a handler is located, its code is executed to provide a workaround. Otherwise, the JVM terminates the application. ■ Caution Code that handles exceptions can be a source of bugs because it’s often not thoroughly tested. Always make sure to test any code that handles exceptions. Apart from being too easy to ignore, an error code’s Boolean or integer value is less meaningful than an object name. For example, fileNotFound is self-explanatory, but what does false mean? Also, an object can contain information about what led to the exception. These details can be helpful to a suitable workaround. The Throwable Class Hierarchy Java provides a hierarchy of classes that represent different kinds of exceptions. These classes are rooted in java.lang.Throwable, the ultimate superclass for all throwables (exception and error objects— exceptions and errors, for short—that can be thrown). Table 3-1 identifies and describes most of Throwable’s constructors and methods. Table 3-1. Throwable’s Constructors and Methods 156 Method Description Throwable() Create a throwable with a null detail message and cause. Throwable(String message) Create a throwable with the specified detail message and a null cause. Throwable(String message, Throwable cause) Create a throwable with the specified detail message and cause. protected Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) Create a throwable with the specified detail message, cause, suppression enabled or disabled, and writable stack trace enabled or disabled. CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Throwable(Throwable cause) Create a throwable whose detail message is the string representation of a nonnull cause, or null. void addSuppressed(Throwable exception) Append the specified exception to the exceptions that were suppressed in order to deliver this exception. Throwable fillInStackTrace() Fill in the execution stack trace. This method records information about the current state of the stack frames for the current thread within this throwable. (I discuss threads in Chapter 4.) Throwable getCause() Return the cause of this throwable. If there is no cause, null is returned. String getMessage() Return this throwable’s detail message, which might be null. StackTraceElement[] getStackTrace() Provide programmatic access to the stack trace information printed by printStackTrace() as an array of stack trace elements, each representing one stack frame. Throwable[] getSuppressed() Return an array containing all exceptions that were suppressed (typically by the try-withresources statement, discussed later) in order to deliver this exception. Throwable initCause(Throwable cause) Initialize the cause of this throwable to the specified value. void printStackTrace() Print this throwable and its backtrace of stack frames to the standard error stream. void setStackTrace(StackTraceElement[] stackTrace) Set the stack trace elements that will be returned by getStackTrace() and printed by printStackTrace() and related methods. It is not uncommon for a class’s public methods to call helper methods that throw various exceptions. A public method will probably not document exceptions thrown from a helper method because they are implementation details that often should not be visible to the public method’s caller. However, because this exception might be helpful in diagnosing the problem, the public method can wrap the lower-level exception in a higher-level exception that is documented in the public method’s contract interface. The wrapped exception is known as a cause because its existence causes the higher-level exception to be thrown. A cause is created by invoking the Throwable(Throwable cause) or Throwable(String message, Throwable cause) constructor, which invoke the initCause() method to 157 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES store the cause. If you do not call either constructor, you can alternatively call initCause() directly, but must do so immediately after creating the throwable. Call the getCause() method to return the cause. When one exception causes another exception, the first exception is usually caught and then the second exception is thrown in response. In other words, there is a causal connection between the two exceptions. In contrast, there are situations where two independent exceptions can be thrown in sibling code blocks; for example, in the try block of a try-with-resources statement (discussed later in this chapter) and the compiler-generated finally block that closes the resource. In these situations, only one of the thrown exceptions can be propagated. In the try-with-resources statement, when there are two such exceptions, the exception originating from the try block is propagated and the exception from the finally block is added (via the addSuppressed() method) to the list of exceptions suppressed by the exception from the try block. As an exception unwinds the stack, it can accumulate multiple suppressed exceptions. An array of the suppressed expressions can be retrieved by calling getSuppressed(). When an exception is thrown, it leaves behind a stack of unfinished method calls. Throwable’s constructors call fillInStackTrace() to record this stack trace information, which is output by calling printStackTrace(). The getStackTrace() method provides programmatic access to the stack trace by returning this information as an array of java.lang.StackTraceElement instances – each instance represents one stack entry. StackTraceElement provides methods to return stack trace information. For example, String getMethodName() returns the name of an unfinished method. The setStackTrace() method is designed for use by Remote Procedure Call (RPC) frameworks (RPC is briefly discussed in Chapter 11) and other advanced systems, allowing the client to override the default stack trace that is generated by fillInStackTrace() when a throwable is constructed, or deserialized when a throwable is read from a serialization stream. (I will discuss serialization in Chapter 8.) Except for Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace), each Throwable constructor always treats suppression as being enabled, and always calls fillInStackTrace(). In contrast, this constructor lets you disable suppression by passing false to enableSuppression, and prevent fillInStackTrace() from being called by passing false to writableStackTrace. Pass false to writableStackTrace when you plan to override the default stack trace and want to avoid the unnecessary fillInStackTrace() method calls. Similarly, pass false to enableSuppression when repeatedly catching and rethrowing the same exception object (to implement control flow between two subsystems, for example) or in other exceptional circumstances. You will notice that Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) is signified as a protected constructor. Also, its Java documentation includes the following sentence: “Subclasses of Throwable should document any conditions under which suppression is disabled and document conditions under which the stack trace is not writable.” This is an example of “design and document for class extension,” which I discuss in Chapter 2. Moving down the throwable hierarchy, you encounter the java.lang.Exception and java.lang.Error classes, which respectively represent exceptions and errors. Each class offers five constructors that pass their arguments to their Throwable counterparts, but provides no methods apart from those that are inherited from Throwable. Exception is itself subclassed by java.lang.CloneNotSupportedException (discussed in Chapter 2), java.io.IOException (discussed in Chapter 8), and other classes. Similarly, Error is itself subclassed by java.lang.AssertionError (discussed later in this chapter), java.lang.OutOfMemoryError, and other classes. 158 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES ■ Caution Never instantiate Throwable, Exception, or Error. The resulting objects are meaningless because they are too generic. This book was purchased by [email protected] Checked Exceptions Versus Runtime Exceptions A checked exception is an exception that represents a problem with the possibility of recovery, and for which the developer must provide a workaround. The compiler checks (examines) the code to ensure that the exception is handled in the method where it is thrown, or is explicitly identified as being handled elsewhere. Exception and all subclasses except for java.lang.RuntimeException (and its subclasses) describe checked exceptions. For example, the CloneNotSupportedException and IOException classes describe checked exceptions. (CloneNotSupportedException should not be checked because there is no runtime workaround for this kind of exception.) A runtime exception is an exception that represents a coding mistake. This kind of exception is also known as an unchecked exception because it does not need to be handled or explicitly identified—the mistake must be fixed. Because these exceptions can occur in many places, it would be burdensome to be forced to handle them. RuntimeException and its subclasses describe unchecked exceptions. For example, java.lang.ArithmeticException describes arithmetic problems such as integer division by zero. Another example is java.lang.ArrayIndexOutOfBoundsException. (In hindsight, RuntimeException should have been named UncheckedException because all exceptions occur at runtime.) ■ Note Many developers are not happy with checked exceptions because of the work involved in having to handle them. This problem is made worse by libraries providing methods that throw checked exceptions when they should throw unchecked exceptions. As a result, many modern languages support only unchecked exceptions. Custom Exception Classes You can declare your own exception classes. Before doing so, ask yourself if an existing exception class in Java’s standard class library meets your needs. If you find a suitable class, you should reuse it. (Why reinvent the wheel?) Other developers will already be familiar with the existing class, and this knowledge will make your code easier to learn. If no existing class meets your needs, think about whether to subclass Exception or RuntimeException. In other words, will your exception class be checked or unchecked? As a rule of thumb, your class should subclass RuntimeException if you think that it will describe a coding mistake. ■ Tip When you name your class, follow the convention of providing an Exception suffix. This suffix clarifies that your class describes an exception. 159 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Suppose you are creating a Media class whose static methods perform various media-oriented utility tasks. For example, one method converts sound files in non-MP3 media formats to MP3 format. This method will be passed source file and destination file arguments, and will convert the source file to the format implied by the destination file’s extension. Before performing the conversion, the method needs to verify that the source file’s format agrees with the format implied by its file extension. If there is no agreement, an exception must be thrown. Furthermore, this exception must store the expected and existing media formats so that a handler can identify them when presenting a message to the user. Because Java’s class library does not provide a suitable exception class, you decide to introduce a class named InvalidMediaFormatException. Detecting an invalid media format is not the result of a coding mistake, and so you also decide to extend Exception to indicate that the exception is checked. Listing 3-24 presents this class’s declaration. Listing 3-24. Declaring a custom exception class package media; public class InvalidMediaFormatException extends Exception { private String expectedFormat; private String existingFormat; public InvalidMediaFormatException(String expectedFormat, String existingFormat) { super("Expected format: "+expectedFormat+", Existing format: "+ existingFormat); this.expectedFormat = expectedFormat; this.existingFormat = existingFormat; } public String getExpectedFormat() { return expectedFormat; } public String getExistingFormat() { return existingFormat; } } InvalidMediaFormatException provides a constructor that calls Exception’s public Exception(String message) constructor with a detail message that includes the expected and existing formats. It is wise to capture such details in the detail message because the problem that led to the exception might be hard to reproduce. InvalidMediaFormatException also provides getExpectedFormat() and getExistingFormat() methods that return these formats. Perhaps a handler will present this information in a message to the user. Unlike the detail message, this message might be localized, expressed in the user’s language (French, German, English, and so on). 160 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Throwing Exceptions Now that you have created an InvalidMediaFormatException class, you can declare the Media class and begin to code its convert() method. The initial version of this method validates its arguments, and then verifies that the source file’s media format agrees with the format implied by its file extension. Check out Listing 3-25. Listing 3-25. Throwing exceptions from the convert() method package media; import java.io.IOException; public final class Media { public static void convert(String srcName, String dstName) throws InvalidMediaFormatException, IOException { if (srcName == null) throw new NullPointerException(srcName+" is null"); if (dstName == null) throw new NullPointerException(dstName+" is null"); // Code to access source file and verify that its format matches the // format implied by its file extension. // // Assume that the source file’s extension is RM (for Real Media) and // that the file’s internal signature suggests that its format is // Microsoft WAVE. String expectedFormat = "RM"; String existingFormat = "WAVE"; throw new InvalidMediaFormatException(expectedFormat, existingFormat); } } Listing 3-25 declares the Media class to be final because this class will only consist of class methods and there’s no reason to extend it. Media’s convert() method appends throws InvalidMediaFormatException, IOException to its header. A throws clause identifies all checked exceptions that are thrown out of the method, and which must be handled by some other method. It consists of reserved word throws followed by a commaseparated list of checked exception class names, and is always appended to a method header. The convert() method’s throws clause indicates that this method is capable of throwing an InvalidMediaFormatException or IOException instance to the JVM. convert() also demonstrates the throw statement, which consists of reserved word throw followed by an instance of Throwable or a subclass. (You typically instantiate an Exception subclass.) This statement throws the instance to the JVM, which then searches for a suitable handler to handle the exception. The first use of the throw statement is to throw a java.lang.NullPointerException instance when a null reference is passed as the source or destination filename. This unchecked exception is commonly thrown to indicate that a contract has been violated via a passed null reference. (Chapter 6’s discussion of the java.util.Objects class presents an alternative approach to dealing with null references passed to parameters.) For example, you cannot pass null filenames to convert(). 161 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES The second use of the throw statement is to throw a media.InvalidMediaFormatException instance when the expected media format does not match the existing format. In the contrived example, the exception is thrown because the expected format is RM and the existing format is WAVE. Unlike InvalidMediaFormatException, NullPointerException is not listed in convert()’s throws clause because NullPointerException instances are unchecked. They can occur so frequently that it is too big a burden to force the developer to properly handle these exceptions. Instead, the developer should write code that minimizes their occurrences. Although not thrown from convert(), IOException is listed in this method’s throws clause in preparation for refactoring this method to perform the conversion with the help of file-handling code. NullPointerException is one kind of exception that is thrown when an argument proves to be invalid. The java.lang.IllegalArgumentException class generalizes the illegal argument scenario to include other kinds of illegal arguments. For example, the following method throws an IllegalArgumentException instance when a numeric argument is negative: public static double sqrt(double x) { if (x < 0) throw new IllegalArgumentException(x+" is negative"); // Calculate the square root of x. } There are a few additional items to keep in mind when working with throws clauses and throw statements: 162 • You can append a throws clause to a constructor and throw an exception from the constructor when something goes wrong while the constructor is executing. The resulting object will not be created. • When an exception is thrown out of an application’s main() method, the JVM terminates the application and calls the exception’s printStackTrace() method to print, to the console, the sequence of nested method calls that was awaiting completion when the exception was thrown. • If a superclass method declares a throws clause, the overriding subclass method does not have to declare a throws clause. However, if the subclass method does declare a throws clause, the clause must not include the names of checked exception classes that are not also included in the superclass method’s throws clause, unless they are the names of exception subclasses. For example, given superclass method void foo() throws IOException {}, the overriding subclass method could be declared as void foo() {}, void foo() throws IOException {}, or void foo() throws FileNotFoundException—the java.io.FileNotFoundException class subclasses IOException. • A checked exception class name does not need to appear in a throws clause when the name of its superclass appears. • The compiler reports an error when a method throws a checked exception and does not also handle the exception or list the exception in its throws clause. • Do not include the names of unchecked exception classes in a throws clause. These names are not required because such exceptions should never occur. Furthermore, they only clutter source code, and possibly confuse someone who is trying to understand that code. CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES • You can declare a checked exception class name in a method’s throws clause without throwing an instance of this class from the method. (Perhaps the method has yet to be fully coded.) However, Java requires that you provide code to handle this exception, even though it is not thrown. Handling Exceptions A method indicates its intention to handle one or more exceptions by specifying a try statement that includes one or more appropriate catch blocks. The try statement consists of reserved word try followed by a brace-delimited body. You place code that throws exceptions into this block. A catch block consists of reserved word catch, followed by a round bracket-delimited singleparameter list that specifies an exception class name, followed by a brace-delimited body. You place code that handles exceptions whose types match the type of the catch block’s parameter list’s exception class parameter in this block. A catch block is specified immediately after a try block. When an exception is thrown, the JVM searches for a handler by first examining the catch block to see whether its parameter type matches or is the superclass type of the exception that has been thrown. If the catch block is found, its body executes and the exception is handled. Otherwise, the JVM proceeds up the method-call stack, looking for the first method whose try statement contains an appropriate catch block. This process continues unless a catch block is found or execution leaves the main() method. The following example illustrates try and catch: try { int x = 1/0; } catch (ArithmeticException ae) { System.out.println("attempt to divide by zero"); } When execution enters the try block, an attempt is made to divide integer 1 by integer 0. The JVM responds by instantiating ArithmeticException and throwing this exception. It then detects the catch block, which is capable of handling thrown ArithmeticException objects, and transfers execution to this block, which invokes System.out.println() to output a suitable message—the exception is handled. Because ArithmeticException is an example of an unchecked exception type, and because unchecked exceptions represent coding mistakes that must be fixed, you typically don’t catch them, as demonstrated previously. Instead, you would fix the problem that led to the thrown exception. ■ Tip You might want to name your catch block parameters using the abbreviated style shown in the preceding section. Not only does this convention result in more meaningful exception-oriented parameter names (ae implies that an ArithmeticException object has been thrown), it can help reduce compiler errors. For example, it is common practice to name a catch block’s parameter e, for convenience. (Why type a long name?) However, the compiler will report an error when a previously declared local variable or parameter also uses e as its name— multiple same-named local variables and parameters cannot exist in the same scope. 163 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Handling Multiple Exception Types You can specify multiple catch blocks after a try block. For example, Listing 3-25’s convert() method specifies a throws clause indicating that convert() can throw InvalidMediaFormatException, which is currently thrown, and IOException, which will be thrown when convert() is refactored. This refactoring will result in convert() throwing IOException when it cannot read from the source file or write to the destination file, and throwing FileNotFoundException (a subclass of IOException) when it cannot open the source file or create the destination file. All these exceptions must be handled, as demonstrated in Listing 3-26. Listing 3-26. Handling different kinds of exceptions import java.io.FileNotFoundException; import java.io.IOException; import media.InvalidMediaFormatException; import media.Media; class Converter { public static void main(String[] args) { if (args.length != 2) { System.err.println("usage: java Converter srcfile dstfile"); return; } try { Media.convert(args[0], args[1]); } catch (InvalidMediaFormatException imfe) { System.out.println("Unable to convert "+args[0]+" to "+args[1]); System.out.println("Expecting "+args[0]+" to conform to "+ imfe.getExpectedFormat()+" format."); System.out.println("However, "+args[0]+" conformed to "+ imfe.getExistingFormat()+" format."); } catch (FileNotFoundException fnfe) { } catch (IOException ioe) { } } } The call to Media’s convert() method in Listing 3-26 is placed in a try block because this method is capable of throwing an instance of the checked InvalidMediaFormatException, IOException, or FileNotFoundException class—checked exceptions must be handled or be declared to be thrown via a throws clause that is appended to the method. 164 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES The catch (InvalidMediaFormatException imfe) block’s statements are designed to provide a descriptive error message to the user. A more sophisticated application would localize these names so that the user could read the message in the user’s language. The developer-oriented detail message is not output because it is not necessary in this trivial application. ■ Note A developer-oriented detail message is typically not localized. Instead, it is expressed in the developer’s language. Users should never see detail messages. Although not thrown, a catch block for IOException is required because this checked exception type appears in convert()’s throws clause. Because the catch (IOException ioe) block can also handle thrown FileNotFoundException instances (because FileNotFoundException subclasses IOException), the catch (FileNotFoundException fnfe) block isn’t necessary at this point, but is present to separate out the handling of a situation where a file cannot be opened for reading or created for writing (which will be addressed once convert() is refactored to include file code). Assuming that the current directory contains Listing 3-26 and a media subdirectory containing InvalidMediaFormatException.java and Media.java, compile this listing (javac Converter.java), which also compiles media’s source files, and run the application, as in java Converter A B. Converter responds by presenting the following output: Unable to convert A to B Expecting A to conform to RM format. However, A conformed to WAVE format. Listing 3-26’s empty FileNotFoundException and IOException catch blocks illustrate the often-seen problem of leaving catch blocks empty because they are inconvenient to code. Unless you have a good reason, do not create an empty catch block. It swallows exceptions and you do not know that the exceptions were thrown. (For brevity, I don’t always code catch blocks in this book’s examples.) ■ Caution The compiler reports an error when you specify two or more catch blocks with the same parameter type after a try body. Example: try {} catch (IOException ioe1) {} catch (IOException ioe2) {}. You must merge these catch blocks into one block. Although you can write catch blocks in any order, the compiler restricts this order when one catch block’s parameter is a supertype of another catch block’s parameter. The subtype parameter catch block must precede the supertype parameter catch block; otherwise, the subtype parameter catch block will never be executed. For example, the FileNotFoundException catch block must precede the IOException catch block. If the compiler allowed the IOException catch block to be specified first, the FileNotFoundException catch block would never execute because a FileNotFoundException instance is also an instance of its IOException superclass. 165 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Multicatch Suppose you have two or more catch blocks whose code is identical or nearly identical. To eliminate this redundancy, you might be tempted to refactor this code into a single catch block with a common superclass exception type (such as catch (Exception e) {}). However, catching overly broad exceptions is not a good idea because doing so masks the purpose for the handler (what exceptions are handled by catch (Exception e) {}, for example). Also, the single catch block might inadvertently handle thrown exceptions that should be handled elsewhere. (Perhaps these exceptions are thrown as a result of refactored code.) Java provides the multicatch language feature to avoid redundancy and also the problems inherent with catching overly broad exceptions. Multicatch lets you specify multiple exception types in a catch block where each successive type is separated from its predecessor by placing a vertical bar (|) between these types. Consider the following example: try { Media.convert(args[0], args[1]); } catch (InvalidMediaFormatException | UnsupportedMediaFormatException imfeumfe) { // common code to respond to these similar exceptions } This example assumes that convert() is also capable of throwing media.UnsupportedMediaFormatException when it detects a media format that it cannot handle (such as a video format). When convert() throws either InvalidMediaFormatException or UnsupportedMediaFormatException, the catch block will handle either exception. When multiple exception types are listed in a catch block’s single parameter list, the parameter is implicitly regarded as final. As a result, you cannot change the parameter’s value. For example, you cannot change the reference stored in the example’s imfeumfe parameter. Multicatch is not always necessary. For example, you do not need to specify catch (FileNotFoundException | IOException fnfeioe) { /* suitable common code */ } to handle FileNotFoundException and IOException because catch (IOException ioe) accomplishes the same task, by catching FileNotFoundException as well as IOException. For this reason, the compiler reports an error when it detects a catch block whose parameter list exception types include a supertype and a subtype. ■ Note The bytecode resulting from compiling a catch block that handles multiple exception types will be smaller than compiling several catch blocks that each handle only one of the listed exception types. A catch block that handles multiple exception types contributes no duplicate bytecode during compilation. In other words, the bytecode doesn’t contain replicated exception handlers. Rethrowing Exceptions While discussing the Throwable class, I discussed wrapping lower-level exceptions in higher-level exceptions. This activity will typically take place in a catch block, and is illustrated in the following example: 166 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES catch (IOException ioe) { throw new ReportCreationException(ioe); } This example assumes that a helper method has just thrown a generic IOException instance as the result of trying to create a report. The public method’s contract states that ReportCreationException is thrown in this case. To satisfy the contract, the latter exception is thrown. To satisfy the developer who is responsible for debugging a faulty application, the IOException instance is wrapped inside the ReportCreationException instance that is thrown to the public method’s caller. Sometimes, a catch block might not be able to fully handle an exception. Perhaps it needs access to information provided by some ancestor method in the method-call stack. However, the catch block might be able to partly handle the exception. In this case, it should partly handle the exception, and then rethrow the exception so that a handler in the ancestor method can finish handling the exception. This scenario is demonstrated in the following example: catch (FileNotFoundException fnfe) { // Provide code to partially handle the exception here. throw fnfe; // Rethrow the exception here. } Final Rethrow Java 7’s compiler analyzes rethrown exceptions more precisely than its predecessors, but only when no assignments are made to the rethrown exception’s catch block parameter (the parameter is effectively final). When an exception originates from the preceding try block and is a supertype/subtype of the parameter’s type, the compiler throws the actual type of the caught exception instead of throwing the type of the parameter (as is done in previous Java versions). The purpose of this final rethrow feature is to facilitate adding a try statement around a block of code to intercept, process, and rethrow an exception without affecting the statically determined set of exceptions thrown from the code. Also, this feature lets you provide a common exception handler to partly handle the exception close to where it’s thrown, and provide more precise handlers elsewhere that handle the rethrown exception. Consider Listing 3-27. Listing 3-27. A pressure simulation class PressureException extends Exception { PressureException(String msg) { super(msg); } } class TemperatureException extends Exception { TemperatureException(String msg) { super(msg); } } 167 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES class MonitorEngine { public static void main(String[] args) { try { monitor(); } catch (Exception e) { if (e instanceof PressureException) System.out.println("correcting pressure problem"); else System.out.println("correcting temperature problem"); } } static void monitor() throws Exception { try { if (Math.random() < 0.1) throw new PressureException("pressure too high"); else if (Math.random() > 0.9) throw new TemperatureException("temperature too high"); else System.out.println("all is well"); } catch (Exception e) { System.out.println(e.getMessage()); throw e; } } } Listing 3-27 simulates the testing of an experimental rocket engine to see if the engine’s pressure or temperature exceeds a safety threshold. It performs this testing via the monitor() helper method. monitor()’s try block throws PressureException when it detects a pressure extreme, and throws TemperatureException when it detects a temperature extreme. (Because this is only a simulation, random numbers are used. I’ll have more to say about random numbers in Chapter 4.) The try block is followed by a catch block, which is designed to partly handle the exception by outputting a warning message. This exception is then rethrown so that monitor()’s calling method can finish handling the exception. Before Java 7, you couldn’t specify PressureException and TemperatureException in monitor()’s throws clause because the catch block’s e parameter is of type Exception and rethrowing an exception was treated as throwing the parameter’s type. Starting with Java 7, you can specify these exception types in the throws clause because the compiler determines that the exception thrown by throw e came from the try block, and only PressureException and TemperatureException can be thrown from this block. Because you can now specify static void monitor() throws PressureException, TemperatureException, you can provide more precise handlers where monitor() is called, as the following example demonstrates: 168 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES try { monitor(); } catch (PressureException pe) { System.out.println("correcting pressure problem"); } catch (TemperatureException te) { System.out.println("correcting temperature problem"); } Because of the improved type checking offered by final rethrow, source code that compiled under previous versions of Java might fail to compile under Java 7. For example, consider Listing 3-28. This book was purchased by [email protected] Listing 3-28. Demonstrating code breakage as a result of final rethrow class SuperException extends Exception { } class SubException1 extends SuperException { } class SubException2 extends SuperException { } class BreakageDemo { public static void main(String[] args) throws SuperException { try { throw new SubException1(); } catch (SuperException se) { try { throw se; } catch (SubException2 se2) { } } } } Listing 3-28 compiles under Java 6 and earlier. However, it fails to compile under Java 7, whose compiler detects and reports the fact that SubException2 is never thrown in the body of the corresponding try statement. 169 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Although unlikely to occur, it’s possible to run into this problem. Instead of grumbling about the breakage, consider the value in having the compiler detect a source of redundant code whose removal results in cleaner source code and a smaller classfile. Performing Cleanup In some situations, you might want to prevent an exception from being thrown out of a method before the method’s cleanup code is executed. For example, you might want to close a file that was opened, but could not be written, possibly because of insufficient disk space. Java provides the finally block for this situation. The finally block consists of reserved word finally followed by a body, which provides the cleanup code. A finally block follows either a catch block or a try block. In the former case, the exception is handled (and possibly rethrown) before finally executes. In the latter case, finally executes before the exception is thrown and handled. Listing 3-29 demonstrates the finally block in the context of a file-copying application. Listing 3-29. Cleaning up after handling a thrown exception import import import import java.io.FileInputStream; java.io.FileOutputStream; java.io.FileNotFoundException; java.io.IOException; class Copy { public static void main(String[] args) { if (args.length != 2) { System.err.println("usage: java Copy srcfile dstfile"); return; } FileInputStream fis = null; try { fis = new FileInputStream(args[0]); FileOutputStream fos = null; try { fos = new FileOutputStream(args[1]); int b; // I chose b instead of byte because byte is a reserved word. while ((b = fis.read()) != -1) fos.write(b); } catch (FileNotFoundException fnfe) { String msg = args[1]+" could not be created, possibly because "+ "it might be a directory"; System.err.println(msg); } catch (IOException ioe) 170 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES { String msg = args[0]+" could not be read, or "+args[1]+ " could not be written"; System.err.println(msg); } finally { if (fos != null) try { fos.close(); } catch (IOException ioe) { System.err.println("unable to close "+args[1]); } } } catch (FileNotFoundException fnfe) { String msg = args[0]+" could not be found or might be a directory"; System.err.println(msg); } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) { System.err.println("unable to close "+args[0]); } } } } ■ Note Do not be concerned if you find this listing’s file-oriented code difficult to grasp; I will formally introduce I/O and the listing’s file-oriented types in Chapter 8. I’m presenting this code here because file copying provides a perfect example of the finally block. Listing 3-29 presents an application that copies bytes from a source file to a destination file via a nested pair of try blocks. The outer try block uses a java.io.FileInputStream object to open the source file for reading; the inner try block uses a java.io.FileOutputStream object to create the destination file for writing, and also contains the file-copying code. 171 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES If the fis = new FileInputStream(args[0]); expression throws FileNotFoundException, execution flows into the outer try statement’s catch (FileNotFoundException fnfe) block, which outputs a suitable message to the user. Execution then enters the outer try statement’s finally block. The outer try statement’s finally block closes an open source file. However, when FileNotFoundException is thrown, the source file is not open—no reference was assigned to fis. The finally block uses if (fis != null) to detect this situation, and does not attempt to close the file. If fis = new FileInputStream(args[0]); succeeds, execution flows into the inner try block, which executes fos = new FileOutputStream(args[1]);. If this expression throws FileNotFoundException, execution moves into the inner try’s catch (FileNotFoundException fnfe) block, which outputs a suitable message to the user. This time, execution continues with the inner try statement’s finally block. Because the destination file was not created, no attempt is made to close this file. In contrast, the open source file must be closed, and this is accomplished when execution moves from the inner finally block to the outer finally block. FileInputStream’s and FileOutputStream’s close() methods throw IOException when a file is not open. Because IOException is checked, these exceptions must be handled; otherwise, it would be necessary to append a throws IOException clause to the main() method header. You can specify a try statement with only a finally block. You would do so when you are not prepared to handle an exception in the enclosing method (or enclosing try statement, if present), but need to perform cleanup before the thrown exception causes execution to leave the method. Listing 3-30 provides a demonstration. Listing 3-30. Cleaning up before handling a thrown exception import import import import java.io.FileInputStream; java.io.FileOutputStream; java.io.FileNotFoundException; java.io.IOException; class Copy { public static void main(String[] args) { if (args.length != 2) { System.err.println("usage: java Copy srcfile dstfile"); return; } try { copy(args[0], args[1]); } catch (FileNotFoundException fnfe) { String msg = args[0]+" could not be found or might be a directory,"+ " or "+args[1]+" could not be created, "+ "possibly because "+args[1]+" is a directory"; System.err.println(msg); } catch (IOException ioe) { 172 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES String msg = args[0]+" could not be read, or "+args[1]+ " could not be written"; System.err.println(msg); } } static void copy(String srcFile, String dstFile) throws IOException { FileInputStream fis = new FileInputStream(srcFile); try { FileOutputStream fos = new FileOutputStream(dstFile); try { int b; while ((b = fis.read()) != -1) fos.write(b); } finally { try { fos.close(); } catch (IOException ioe) { System.err.println("unable to close "+dstFile); } } } finally { try { fis.close(); } catch (IOException ioe) { System.err.println("unable to close "+srcFile); } } } } Listing 3-30 provides an alternative to Listing 3-29 that attempts to be more readable. It accomplishes this task by introducing a copy() method that uses a nested pair of try-finally constructs to perform the file-copy operation, and also close each open file whether an exception is or is not thrown. If the FileInputStream fis = new FileInputStream(srcFile); expression results in a thrown FileNotFoundException, execution leaves copy() without entering the outer try statement. This statement is only entered after the FileInputStream object has been created, indicating that the source file was opened. If the FileOutputStream fos = new FileOutputStream(dstFile); expression results in a thrown FileNotFoundException, execution leaves copy() without entering the inner try statement. However, 173 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES execution leaves copy() only after entering the finally block that is mated with the outer try block. This finally block closes the open source file. If the read() or write() method in the inner try statement’s body throws an IOException object, the finally block associated with the inner try block is executed. This finally block closes the open destination file. Execution then flows into the outer finally block, which closes the open source file, and continues on out of copy(). ■ Caution If the body of a try statement throws an exception, and if the finally block results in another exception being thrown, this new exception replaces the previous exception, which is lost. Despite Listing 3-30 being somewhat more readable than Listing 3-29, there is still a lot of boilerplate thanks to each finally block requiring a try statement to close a file. This boilerplate is necessary; its removal results in a new IOException possibly being thrown from the catch block, which would mask a previously thrown IOException. Automatic Resource Management Listings 3-29 and 3-30 are hideous because of the amount of code that’s necessary to ensure that each file is closed. However, you don’t have to code this way. Instead, you can use Java’s try-with-resources statement to automatically close resources (objects that must be closed when they are no longer needed) on your behalf. The try-with-resources statement minimally consists of a try block that features the following syntax: try ([resource declaration; ...] resource declaration) { // code to execute } Reserved word try is followed by a round bracket-delimited and semicolon-separated list of resource declarations. Each of the declared resources is to be closed when execution leaves the try block, either normally or via a thrown exception. The following example uses try-with-resources to shorten Listing 3-30’s copy() method considerably: static void copy(String srcFile, String dstFile) throws IOException { try (FileInputStream fis = new FileInputStream(srcFile); FileOutputStream fos = new FileOutputStream(dstFile)) { int b; while ((b = fis.read()) != -1) fos.write(b); } } The example’s try-with-resources statement declares two file resources that must be closed; the resource declarations are separated with a mandatory semicolon. When the copy() method ends 174 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES (normally or via a thrown exception), fis’s and fos’s close() methods are called, but in the opposite order to which these resources were created (fis was created before fos). Hence, fos.close() is called before fis.close(). Suppose that fos.write(buffer, 0, n) throws an IOException instance. Now suppose that the behind-the-scenes fos.close() method call results in a thrown IOException instance. This latter exception is suppressed, and the exception thrown by fos.write(buffer, 0, n) is the exception thrown out of the copy() method. The suppressed exception can be retrieved by calling Throwable’s Throwable[] getSuppressed() method, which I previously presented. ■ Note A try-with-resources statement can include catch and finally. These blocks are executed after all declared resources have been closed. To take advantage of try-with-resources with your own classes, keep in mind that a resource class must implement the java.lang.AutoCloseable interface or its java.lang.Closeable subinterface. Each interface provides a close() method that performs the close operation. Unlike Closeable’s close() method, which is declared to throw only IOException (or a subtype), AutoCloseable’s close() method is declared to throw Exception. As a result, classes that implement AutoCloseable, Closeable, or a subinterface can declare their close() methods to throw any kind of exception. The close() method should be declared to throw a more specific exception, or (as with java.util.Scanner’s close() method) to not throw an exception if the method cannot fail. ■ Note Implementations of Closeable’s close() method are idempotent; subsequent calls to close() have no effect on the resource. In contrast, implementations of AutoCloseable’s close() method are not required to be idempotent, but making them idempotent is recommended. Assertions Writing source code is not an easy task. All too often, bugs (defects) are introduced into the code. When a bug is not discovered before compiling the source code, it makes it into runtime code, which will probably fail unexpectedly. At this point, the cause of failure can be very difficult to determine. Developers often make assumptions about application correctness, and some developers think that specifying comments that state their beliefs about what they think is true at the comment locations is sufficient for determining correctness. However, comments are useless for preventing bugs because the compiler ignores them. Many languages address this problem by providing a language feature called assertions that lets the developer codify assumptions about application correctness. When the application runs, and if an assertion fails, the application terminates with a message that helps the developer diagnose the failure’s cause. This section introduces you to Java’s assertions language feature. After defining this term, showing you how to declare assertions, and providing examples, the section looks at using and avoiding 175 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES assertions. Finally, you learn how to selectively enable and disable assertions via the javac compiler tool’s command-line arguments. Declaring Assertions An assertion is a statement that lets you express an assumption of program correctness via a Boolean expression. If this expression evaluates to true, execution continues with the next statement. Otherwise, an error that identifies the cause of failure is thrown. There are two forms of the assertion statement, each of which begins with reserved word assert: assert expression1 ; assert expression1 : expression2 ; In both forms of this statement, expression1 is the Boolean expression. In the second form, expression2 is any expression that returns a value. It cannot be a call to a method whose return type is void. When expression1 evaluates to false, this statement instantiates the AssertionError class. The first statement form calls this class’s noargument constructor, which does not associate a message identifying failure details with the AssertionError instance. The second form calls an AssertionError constructor whose type matches the type of expression2’s value. This value is passed to the constructor and its string representation is used as the error’s detail message. When the error is thrown, the name of the source file and the number of the line from where the error was thrown are output to the console as part of the thrown error’s stack trace. In many situations, this information is sufficient for identifying what led to the failure, and the first form of the assertion statement should be used. Listing 3-31 demonstrates the first form of the assertion statement. Listing 3-31. Throwing an assertion error without a detail message class AssertionDemo { public static void main(String[] args) { int x = 1; assert x == 0; } } When assertions are enabled (I discuss this task later), running the previous application results in the following output: Exception in thread "main" java.lang.AssertionError at AssertionDemo.main(AssertionDemo.java:6) In other situations, more information is needed to help diagnose the cause of failure. For example, suppose expression1 compares variables x and y, and throws an error when x’s value exceeds y’s value. Because this should never happen, you would probably use the second statement form to output these values so you could diagnose the problem. Listing 3-32 demonstrates the second form of the assertion statement. 176 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-32. Throwing an assertion error with a detail message class AssertionDemo { public static void main(String[] args) { int x = 1; assert x == 0: x; } } Once again, it is assumed that assertions are enabled. Running the previous application results in the following output: Exception in thread "main" java.lang.AssertionError: 1 at AssertionDemo.main(AssertionDemo.java:6) The value in x is appended to the end of the first output line, which is somewhat cryptic. To make this output more meaningful, you might want to specify an expression that also includes the variable’s name: assert x == 0: "x = "+x;, for example. Using Assertions There are many situations where assertions should be used. These situations organize into internal invariant, control-flow invariant, and design-by-contract categories. An invariant is something that does not change. Internal Invariants An internal invariant is expression-oriented behavior that is not expected to change. For example, Listing 3-33 introduces an internal invariant by way of chained if-else statements that output the state of water based on its temperature. Listing 3-33. Discovering that an internal invariant can vary class IIDemo { public static void main(String[] args) { double temperature = 50.0; // Celsius if (temperature < 0.0) System.out.println("water has solidified"); else if (temperature >= 100.0) System.out.println("water is boiling into a gas"); else { // temperature > 0.0 and temperature < 100.0 assert(temperature > 0.0 && temperature < 100.0): temperature; System.out.println("water is remaining in its liquid state"); 177 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES } } } A developer might specify only a comment stating an assumption as to what expression causes the final else to be reached. Because the comment might not be enough to detect the lurking < 0.0 expression bug, an assertion statement is necessary. Another example of an internal invariant concerns a switch statement with no default case. The default case is avoided because the developer believes that all paths have been covered. However, this is not always true, as Listing 3-34 demonstrates. Listing 3-34. Another buggy internal invariant class IIDemo { final static int NORTH = 0; final static int SOUTH = 1; final static int EAST = 2; final static int WEST = 3; public static void main(String[] args) { int direction = (int) (Math.random()*5); switch (direction) { case NORTH: System.out.println("travelling case SOUTH: System.out.println("travelling case EAST : System.out.println("travelling case WEST : System.out.println("travelling default : assert false; } } } north"); break; south"); break; east"); break; west"); break; Listing 3-34 assumes that the expression tested by switch will only evaluate to one of four integer constants. However, (int) (Math.random()*5) can also return 4, causing the default case to execute assert false;, which always throws AssertionError. (You might have to run this application a few times to see the assertion error, but first you need to learn how to enable assertions, which I discuss later in this chapter.) ■ Tip When assertions are disabled, assert false; does not execute and the bug goes undetected. To always detect this bug, replace assert false; with throw new AssertionError(direction);. Control-Flow Invariants A control-flow invariant is a flow of control that is not expected to change. For example, Listing 3-34 uses an assertion to test an assumption that switch’s default case will not execute. Listing 3-35, which fixes Listing 3-34’s bug, provides another example. 178 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES This book was purchased by [email protected] Listing 3-35. A buggy control-flow invariant class CFDemo { final static int NORTH = 0; final static int SOUTH = 1; final static int EAST = 2; final static int WEST = 3; public static void main(String[] args) { int direction = (int)(Math.random()*4); switch (direction) { case NORTH: System.out.println("travelling case SOUTH: System.out.println("travelling case EAST : System.out.println("travelling case WEST : System.out.println("travelling default : assert false; } } } north"); break; south"); break; east"); break; west"); Because the original bug has been fixed, the default case should never be reached. However, the omission of a break statement that terminates case WEST causes execution to reach the default case. This control-flow invariant has been broken. (Again, you might have to run this application a few times to see the assertion error, but first you need to learn how to enable assertions, which I discuss later in this chapter.) ■ Caution Be careful when using an assertion statement to detect code that should never be executed. If the assertion statement cannot be reached according to the rules set forth in The Java Language Specification, Third Edition, by James Gosling, Bill Joy, Guy Steele, and Gilad Bracha (Addison-Wesley, 2005; ISBN: 0321246780) (also available at (http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html), the compiler will report an error. For example, for(;;); assert false; causes the compiler to report an error because the infinite for loop prevents the assertion statement from executing. Design-by-Contract Design-by-Contract (see http://en.wikipedia.org/wiki/Design_by_contract) is a way to design software based on preconditions, postconditions, and invariants (internal, control-flow, and class). Assertion statements support an informal design-by-contract style of development. 179 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Preconditions A precondition is something that must be true when a method is called. Assertion statements are often used to satisfy a helper method’s preconditions by checking that its arguments are legal. Listing 3-36 provides an example. Listing 3-36. Verifying a precondition class Lotto649 { public static void main(String[] args) { // Lotto 649 requires that six unique numbers be chosen. int[] selectedNumbers = new int[6]; // Assign a unique random number from 1 to 49 (inclusive) to each slot // in the selectedNumbers array. for (int slot = 0; slot < selectedNumbers.length; slot++) { int num; // Obtain a random number from 1 to 49. That number becomes the // selected number if it has not previously been chosen. try_again: do { num = rnd(49)+1; for (int i = 0; i < slot; i++) if (selectedNumbers[i] == num) continue try_again; break; } while (true); // Assign selected number to appropriate slot. selectedNumbers[slot] = num; } // Sort all selected numbers into ascending order and then print these // numbers. sort(selectedNumbers); for (int i = 0; i < selectedNumbers.length; i++) System.out.print(selectedNumbers[i]+" "); } static int rnd(int limit) { // This method returns a random number (actually, a pseudorandom number) // ranging from 0 through limit-1 (inclusive). assert limit > 1: "limit = "+limit; return (int) (Math.random()*limit); } static void sort(int[] x) { // This method sorts the integers in the passed array into ascending // order. for (int pass = 0; pass < x.length-1; pass++) 180 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES for (int i = x.length-1; i > pass; i--) if (x[i] < x[pass]) { int temp = x[i]; x[i] = x[pass]; x[pass] = temp; } } } Listing 3-36’s application simulates Lotto 6/49, one of Canada’s national lottery games. The rnd() helper method returns a randomly chosen integer between 0 and limit-1. An assertion statement verifies the precondition that limit’s value must be 2 or higher. ■ Note The sort() helper method sorts (orders) the selectedNumbers array’s integers into ascending order by implementing an algorithm (a recipe for accomplishing some task) called Bubble Sort. Bubble Sort works by making multiple passes over the array. During each pass, various comparisons and swaps ensure that the next smallest element value “bubbles” toward the top of the array, which would be the element at index 0. Bubble Sort is not efficient, but is more than adequate for sorting a six-element array. Although I could have used one of the efficient sort() methods located in the java.util package’s Arrays class (for example, Arrays.sort(selectedNumbers); accomplishes the same objective as Listing 3-36’s sort(selectedNumbers); method call, but does so more efficiently), I chose to use Bubble Sort because I prefer to wait until Chapter 5 before getting into the Arrays class. Postconditions A postcondition is something that must be true after a method successfully completes. Assertion statements are often used to satisfy a helper method’s postconditions by checking that its result is legal. Listing 3-37 provides an example. Listing 3-37. Verifying a postcondition as well as preconditions class MergeArrays { public static void main(String[] args) { int[] x = { 1, 2, 3, 4, 5 }; int[] y = { 1, 2, 7, 9 }; int[] result = merge(x, y); for (int i = 0; i < result.length; i++) 181 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES System.out.println(result[i]); } static int[] merge(int[] a, int[] b) { if (a == null) throw new NullPointerException("a is null"); if (b == null) throw new NullPointerException("b is null"); int[] result = new int[a.length+b.length]; // Precondition assert result.length == a.length+b.length: "length mismatch"; for (int i = 0; i < a.length; i++) result[i] = a[i]; for (int i = 0; i < b.length; i++) result[a.length+i-1] = b[i]; // Postcondition assert containsAll(result, a, b): "value missing from array"; return result; } static boolean containsAll(int[] result, int[] a, int[] b) { for (int i = 0; i < a.length; i++) if (!contains(result, a[i])) return false; for (int i = 0; i < b.length; i++) if (!contains(result, b[i])) return false; return true; } static boolean contains(int[] a, int val) { for (int i = 0; i < a.length; i++) if (a[i] == val) return true; return false; } } Listing 3-37 uses an assertion statement to verify the postcondition that all the values in the two arrays being merged are present in the merged array. The postcondition is not satisfied, however, because this listing contains a bug. Listing 3-37 also shows preconditions and postconditions being used together. The solitary precondition verifies that the merged array length equals the lengths of the arrays being merged prior to the merge logic. Class Invariants A class invariant is a kind of internal invariant that applies to every instance of a class at all times, except when an instance is transitioning from one consistent state to another. For example, suppose instances of a class contain arrays whose values are sorted in ascending order. You might want to include an isSorted() method in the class that returns true if the array is still 182 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES sorted, and verify that each constructor and method that modifies the array specifies assert isSorted(); prior to exit, to satisfy the assumption that the array is still sorted when the constructor/method exists. Avoiding Assertions Although there are many situations where assertions should be used, there also are situations where they should be avoided. For example, you should not use assertions to check the arguments that are passed to public methods, for the following reasons: • Checking a public method’s arguments is part of the contract that exists between the method and its caller. If you use assertions to check these arguments, and if assertions are disabled, this contract is violated because the arguments will not be checked. • Assertions also prevent appropriate exceptions from being thrown. For example, when an illegal argument is passed to a public method, it is common to throw IllegalArgumentException or NullPointerException. However, AssertionError is thrown instead. You should also avoid using assertions to perform work required by the application to function correctly. This work is often performed as a side effect of the assertion’s Boolean expression. When assertions are disabled, the work is not performed. For example, suppose you have a list of Employee objects and a few null references that are also stored in this list, and you want to remove all the null references. It would not be correct to remove these references via the following assertion statement: assert employees.removeAll(null); Although the assertion statement will not throw AssertionError because there is at least one null reference in the employees list, the application that depends upon this statement executing will fail when assertions are disabled. Instead of depending on the former code to remove the null references, you would be better off using code similar to the following: boolean allNullsRemoved = employees.removeAll(null); assert allNullsRemoved; This time, all null references are removed regardless of whether assertions are enabled or disabled, and you can still specify an assertion to verify that nulls were removed. Enabling and Disabling Assertions The compiler records assertions in the classfile. However, assertions are disabled at runtime because they can affect performance. An assertion might call a method that takes awhile to complete, and this would impact the running application’s performance. You must enable the classfile’s assertions before you can test assumptions about the behaviors of your classes. Accomplish this task by specifying the -enableassertions or -ea command-line option when running the java application launcher tool. The -enableassertions and -ea command-line options let you enable assertions at various granularities based upon one of the following arguments (except for the noargument scenario, you must use a colon to separate the option from its argument): 183 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES • No argument: Assertions are enabled in all classes except system classes. • PackageName...: Assertions are enabled in the specified package and its subpackages by specifying the package name followed by .... • ...: Assertions are enabled in the unnamed package, which happens to be whatever directory is current. • ClassName: Assertions are enabled in the named class by specifying the class name. For example, you can enable all assertions except system assertions when running the MergeArrays application via java -ea MergeArrays. Also, you could enable any assertions in this chapter’s logging package by specifying java -ea:logging TestLogger. Assertions can be disabled, and also at various granularities, by specifying either of the disableassertions or -da command-line options. These options take the same arguments as enableassertions and -ea. For example, java -ea -da:loneclass mainclass enables all assertions except for those in loneclass. (loneclass and mainclass are placeholders for the actual classes that you specify.) The previous options apply to all classloaders (discussed in Appendix C). Except when taking no arguments, they also apply to system classes. This exception simplifies the enabling of assertion statements in all classes except for system classes, which is often desirable. To enable system assertions, specify either -enablesystemassertions or -esa; for example, java -esa -ea:logging TestLogger. Specify either -disablesystemassertions or -dsa to disable system assertions. Annotations While developing a Java application, you might want to annotate various application elements, or associate metadata (data that describes other data) with them. For example, you might want to identify methods that are not fully implemented so that you will not forget to implement them. Java’s annotations language feature lets you accomplish this task. This section introduces you to annotations. After defining this term and presenting three kinds of compiler-supported annotations as examples, the section shows you how to declare your own annotation types and use these types to annotate source code. Finally, you discover how to process your own annotations to accomplish useful tasks. ■ Note Java has always supported ad hoc annotation mechanisms. For example, the java.lang.Cloneable interface identifies classes whose instances can be shallowly cloned via Object’s clone() method, the transient reserved word marks fields that are to be ignored during serialization (discussed in Chapter 8), and the @deprecated javadoc tag documents methods that are no longer supported. Java 6 formalized the need for annotations by introducing the annotations language feature. 184 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Discovering Annotations An annotation is an instance of an annotation type and associates metadata with an application element. It is expressed in source code by prefixing the type name with the @ symbol. For example, @Readonly is an annotation and Readonly is its type. ■ Note You can use annotations to associate metadata with constructors, fields, local variables, methods, packages, parameters, and types (annotation, class, enum, and interface). The compiler supports the Override, Deprecated, SuppressWarnings, and SafeVarargs annotation types. These types are located in the java.lang package. @Override annotations are useful for expressing that a subclass method overrides a method in the superclass, and does not overload that method instead. The following example reveals this annotation being used to prefix the overriding method: @Override public void draw(int color) { // drawing code } @Deprecated annotations are useful for indicating that the marked application element is deprecated (phased out) and should no longer be used. The compiler warns you when a deprecated application element is accessed by nondeprecated code. In contrast, the @deprecated javadoc tag and associated text warns you against using the deprecated item, and tells you what to use instead. The following example demonstrates that @Deprecated and @deprecated can be used together: /** * Allocates a <code>Date</code> object and initializes it so that * it represents midnight, local time, at the beginning of the day * specified by the <code>year</code>, <code>month</code>, and * <code>date</code> arguments. * * @param year the year minus 1900. * @param month the month between 0-11. * @param date the day of the month between 1-31. * @see java.util.Calendar * @deprecated As of JDK version 1.1, * replaced by <code>Calendar.set(year + 1900, month, date)</code> * or <code>GregorianCalendar(year + 1900, month, date)</code>. */ @Deprecated public Date(int year, int month, int date) { this(year, month, date, 0, 0, 0); } 185 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES This example excerpts one of the constructors in Java’s Date class (located in the java.util package). Its Javadoc comment reveals that Date(int year, int month, int date) has been deprecated in favor of using the set() method in the Calendar class (also located in the java.util package). (I explore Date and Calendar in Appendix C.) The compiler suppresses warnings when a compilation unit (typically a class or interface) refers to a deprecated class, method, or field. This feature lets you modify legacy APIs without generating deprecation warnings, and is demonstrated in Listing 3-38. Listing 3-38. Referencing a deprecated field from within the same class declaration class Employee { /** * Employee’s name * @deprecated New version uses firstName and lastName fields. */ @Deprecated String name; String firstName; String lastName; public static void main(String[] args) { Employee emp = new Employee(); emp.name = "John Doe"; } } Listing 3-38 declares an Employee class with a name field that has been deprecated. Although Employee’s main() method refers to name, the compiler will suppress a deprecation warning because the deprecation and reference occur in the same class. Suppose you refactor this listing by introducing a new UseEmployee class and moving Employee’s main() method to this class. Listing 3-39 presents the resulting class structure. Listing 3-39. Referencing a deprecated field from within another class declaration class Employee { /** * Employee’s name * @deprecated New version uses firstName and lastName fields. */ @Deprecated String name; String firstName; String lastName; } class UseEmployee { public static void main(String[] args) { Employee emp = new Employee(); emp.name = "John Doe"; 186 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES } } If you attempt to compile this source code via the javac compiler tool, you will discover the following messages: Note: Employee.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. You will need to specify -Xlint:deprecation as one of javac’s command-line arguments (as in javac -Xlint:deprecation Employee.java) to discover the deprecated item and the code that refers to this item: Employee.java:17: warning: [deprecation] name in Employee has been deprecated emp.name = "John Doe"; ^ 1 warning @SuppressWarnings annotations are useful for suppressing deprecation or unchecked warnings via a "deprecation" or "unchecked" argument. (Unchecked warnings occur when mixing code that uses generics with pre-generics legacy code. I discuss generics and unchecked warnings later in this chapter.) For example, Listing 3-40 uses @SuppressWarnings with a "deprecation" argument to suppress the compiler’s deprecation warnings when code within the UseEmployee class’s main() method accesses the Employee class’s name field. Listing 3-40. Suppressing the previous deprecation warning class Employee { /** * Employee’s name * @deprecated New version uses firstName and lastName fields. */ @Deprecated String name; String firstName; String lastName; } class UseEmployee { @SuppressWarnings("deprecation") public static void main(String[] args) { Employee emp = new Employee(); emp.name = "John Doe"; } } 187 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES ■ Note As a matter of style, you should always specify @SuppressWarnings on the most deeply nested element where it is effective. For example, if you want to suppress a warning in a particular method, you should annotate that method rather than its class. Finally, @SafeVarargs annotations are useful for asserting that the body of the annotated method or constructor does not perform potentially unsafe operations on its variable number of arguments parameter. I’ll have more to say about this annotation when I present generics later in this chapter. Declaring Annotation Types and Annotating Source Code Before you can annotate source code, you need annotation types that can be instantiated. Java supplies many annotation types as well as Override, Deprecated, SuppressWarnings, and SafeVarargs. Java also lets you declare your own types. You declare an annotation type by specifying the @ symbol, immediately followed by reserved word interface, followed by the type’s name, followed by a body. For example, Listing 3-41 uses @interface to declare an annotation type named Stub. Listing 3-41. Declaring the Stub annotation type public @interface Stub { } Instances of annotation types that supply no data apart from a name – their bodies are empty – are known as marker annotations because they mark application elements for some purpose. As Listing 3-42 reveals, @Stub is used to mark empty methods (stubs). Listing 3-42. Annotating a stubbed-out method public class Deck // Describes a deck of cards. { @Stub public void shuffle() { // This method is empty and will presumably be filled in with appropriate // code at some later date. } } Listing 3-42’s Deck class declares an empty shuffle() method. This fact is indicated by instantiating Stub and prefixing shuffle()’s method header with the resulting @Stub annotation. ■ Note Although marker interfaces (introduced in Chapter 2) appear to have been replaced by marker annotations, this is not the case, because marker interfaces have advantages over marker annotations. One advantage is that a 188 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES marker interface specifies a type that is implemented by a marked class, which lets you catch problems at compile time. For example, if a class does not implement the Cloneable interface, its instances cannot be shallowly cloned via Object’s clone() method. If Cloneable had been implemented as a marker annotation, this problem would not be detected until runtime. Although marker annotations are useful (@Override and @Deprecated are good examples), you will typically want to enhance an annotation type so that you can store metadata via its instances. You accomplish this task by adding elements to the type. An element is a method header that appears in the annotation type’s body. It cannot have parameters or a throws clause, and its return type must be a primitive type (such as int), String, Class, an enum, an annotation type, or an array of the preceding types. However, it can have a default value. Listing 3-43 adds three elements to Stub. This book was purchased by [email protected] Listing 3-43. Adding three elements to the Stub annotation type public @interface Stub { int id(); // A semicolon must terminate an element declaration. String dueDate(); String developer() default "unassigned"; } The id() element specifies a 32-bit integer that identifies the stub. The dueDate() element specifies a String-based date that identifies when the method stub is to be implemented. Finally, developer() specifies the String-based name of the developer responsible for coding the method stub. Unlike id() and dueDate(), developer() is declared with a default value, "unassigned". When you instantiate Stub and do not assign a value to developer() in that instance, as is the case with Listing 3-44, this default value is assigned to developer(). Listing 3-44. Initializing a Stub instance’s elements public class Deck { @Stub ( id = 1, dueDate = "12/21/2012" ) public void shuffle() { } } Listing 3-44 reveals one @Stub annotation that initializes its id() element to 1 and its dueDate() element to "12/21/2012". Each element name does not have a trailing (), and the comma-separated list of two element initializers appears between ( and ). Suppose you decide to replace Stub’s id(), dueDate(), and developer() elements with a single String value() element whose string specifies comma-separated ID, due date, and developer name values. Listing 3-45 shows you two ways to initialize value. 189 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-45. Initializing each Stub instance’s value() element public class Deck { @Stub(value = "1,12/21/2012,unassigned") public void shuffle() { } @Stub("2,12/21/2012,unassigned") public Card[] deal(int ncards) { return null; } } Listing 3-45 reveals special treatment for the value() element. When it is an annotation type’s only element, you can omit value()’s name and = from the initializer. I used this fact to specify @SuppressWarnings("deprecation") in Listing 3-40. Using Meta-Annotations in Annotation Type Declarations Each of the Override, Deprecated, and SuppressWarnings annotation types is itself annotated with metaannotations (annotations that annotate annotation types). For example, Listing 3-46 shows you that the SuppressWarnings annotation type is annotated with two meta-annotations. Listing 3-46. The annotated SuppressWarnings type declaration @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings The Target annotation type, which is located in the java.lang.annotation package, identifies the kinds of application elements to which an annotation type applies. @Target indicates that @SuppressWarnings annotations can be used to annotate types, fields, methods, parameters, constructors, and local variables. Each of TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, and LOCAL_VARIABLE is a member of the ElementType enum, which is also located in the java.lang.annotation package. The { and } characters surrounding the comma-separated list of values assigned to Target’s value() element signify an array—value()’s return type is String[]. Although these braces are necessary (unless the array consists of one item), value= could be omitted when initializing @Target because Target declares only a value() element. The Retention annotation type, which is located in the java.lang.annotation package, identifies the retention (also known as lifetime) of an annotation type’s annotations. @Retention indicates that @SuppressWarnings annotations have a lifetime that is limited to source code—they do not exist after compilation. SOURCE is one of the members of the RetentionPolicy enum (located in the java.lang.annotation package). The other members are CLASS and RUNTIME. These three members specify the following retention policies: • 190 CLASS: The compiler records annotations in the classfile, but the JVM does not retain them (to save memory space). This policy is the default. CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES • RUNTIME: The compiler records annotations in the classfile, and the JVM retains them so that they can be read via the Reflection API (discussed in Chapter 4) at runtime. • SOURCE: The compiler discards annotations after using them. There are two problems with the Stub annotation types shown in Listings 3-41 and 3-43. First, the lack of an @Target meta-annotation means that you can annotate any application element @Stub. However, this annotation only makes sense when applied to methods and constructors. Check out Listing 3-47. Listing 3-47. Annotating undesirable application elements @Stub("1,12/21/2012,unassigned") public class Deck { @Stub("2,12/21/2012,unassigned") private Card[] cardsRemaining = new Card[52]; @Stub("3,12/21/2012,unassigned") public Deck() { } @Stub("4,12/21/2012,unassigned") public void shuffle() { } @Stub("5,12/21/2012,unassigned") public Card[] deal(@Stub("5,12/21/2012,unassigned") int ncards) { return null; } } Listing 3-47 uses @Stub to annotate the Deck class, the cardsRemaining field, and the ncards parameter as well as annotating the constructor and the two methods. The first three application elements are inappropriate to annotate because they are not stubs. You can fix this problem by prefixing the Stub annotation type declaration with @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) so that Stub only applies to methods and constructors. After doing this, the javac compiler tool will output the following error messages when you attempt to compile Listing 3-47: Deck.java:1: error: annotation type not applicable to this kind of declaration @Stub("1,12/21/2012,unassigned") ^ Deck.java:4: error: annotation type not applicable to this kind of declaration @Stub("2,12/21/2012,unassigned") ^ Deck.java:15: error: annotation type not applicable to this kind of declaration public Card[] deal(@Stub("5,12/21/2012,unassigned") int ncards) ^ 3 errors 191 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES The second problem is that the default CLASS retention policy makes it impossible to process @Stub annotations at runtime. You can fix this problem by prefixing the Stub type declaration with @Retention(RetentionPolicy.RUNTIME). Listing 3-48 presents the Stub annotation type with the desired @Target and @Retention metaannotations. Listing 3-48. A revamped Stub annotation type import import import import java.lang.annotation.ElementType; java.lang.annotation.Retention; java.lang.annotation.RetentionPolicy; java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) public @interface Stub { String value(); } ■ Note Java also provides Documented and Inherited meta-annotation types in the java.lang.annotation package. Instances of @Documented-annotated annotation types are to be documented by javadoc and similar tools, whereas instances of @Inherited-annotated annotation types are automatically inherited. According to Inherited’s Java documentation, if “the user queries the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class’s superclass will automatically be queried for the annotation type. This process will be repeated until an annotation for this type is found, or the top of the class hierarchy (Object) is reached. If no superclass has an annotation for this type, then the query will indicate that the class in question has no such annotation.” Processing Annotations It is not enough to declare an annotation type and use that type to annotate source code. Unless you do something specific with those annotations, they remain dormant. One way to accomplish something specific is to write an application that processes the annotations. Listing 3-49’s StubFinder application does just that. Listing 3-49. The StubFinder application import java.lang.reflect.Method; class StubFinder { public static void main(String[] args) throws Exception { 192 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES if (args.length != 1) { System.err.println("usage: java StubFinder classfile"); return; } Method[] methods = Class.forName(args[0]).getMethods(); for (int i = 0; i < methods.length; i++) if (methods[i].isAnnotationPresent(Stub.class)) { Stub stub = methods[i].getAnnotation(Stub.class); String[] components = stub.value().split(","); System.out.println("Stub ID = "+components[0]); System.out.println("Stub Date = "+components[1]); System.out.println("Stub Developer = "+components[2]); System.out.println(); } } } StubFinder loads a classfile whose name is specified as a command-line argument, and outputs the metadata associated with each @Stub annotation that precedes each public method header. These annotations are instances of Listing 3-48’s Stub annotation type. StubFinder next uses a special class named Class (in the java.lang package) and its forName() class method to load a classfile. Class also provides a getMethods() method that returns an array of Method objects describing the loaded class’s public methods. For each loop iteration, a Method object’s isAnnotationPresent() method is called to determine if the method is annotated with the annotation described by the Stub class (referred to as Stub.class). If isAnnotationPresent() returns true, Method’s getAnnotation() method is called to return the annotation Stub instance. This instance’s value() method is called to retrieve the string stored in the annotation. Next, String’s split() method is called to split the string’s comma-separated list of ID, date, and developer values into an array of String objects. Each object is then output along with descriptive text. Class’s forName() method is capable of throwing various exceptions that must be handled or explicitly declared as part of a method’s header. For simplicity, I chose to append a throws Exception clause to the main() method’s header. ■ Caution There are two problems with throws Exception. First, it is better to handle the exception and present a suitable error message than to “pass the buck” by throwing it out of main(). Second, Exception is generic—it hides the names of the kinds of exceptions that are thrown. However, it is convenient to specify throws Exception in a throwaway utility. Do not be concerned if you do not understand Class, forName(), getMethods(), Method, isAnnotationPresent(), .class, getAnnotation(), and split(). You will learn about these items in Chapter 4. 193 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES After compiling StubFinder (javac StubFinder.java), Stub (javac Stub.java), and Listing 3-45’s Deck class (javac Deck.java), run StubFinder with Deck as its single command-line argument (java StubFinder Deck). You will observe the following output: Stub ID = 2 Stub Date = 12/21/2012 Stub Developer = unassigned Stub ID = 1 Stub Date = 12/21/2012 Stub Developer = unassigned If you expected the output to reflect the order of appearance of @Stub annotations in Deck.java, you are probably surprised by the output’s unsorted order. This lack of order is caused by getMethods(). According to this method’s Java documentation, “the elements in the array returned are not sorted and are not in any particular order.” ■ Note Java 5 introduced an apt tool for processing annotations. This tool’s functionality has been integrated into the compiler beginning with Java 6 – apt is being phased out. My “Java Tools Annotation Processors” tutorial (http://tutortutor.ca/cgi-bin/makepage.cgi?/tutorials/ct/jtap) provides a tutorial on using the Java compiler to process annotations. Generics Java 5 introduced generics, language features for declaring and using type-agnostic classes and interfaces. When working with Java’s Collections Framework (which I introduce in Chapter 5), these features help you avoid thrown instances of the java.lang.ClassCastException class. ■ Note Although the main use for generics is the Collections Framework, Java’s class library also contains generified (retrofitted to make use of generics) classes that have nothing to do with this framework: java.lang.Class, java.lang.ThreadLocal, and java.lang.ref.WeakReference are three examples. This section introduces you to generics. You first learn how generics promote type safety in the context of the Collections Framework classes, and then you explore generics in the contexts of generic types and generic methods. After learning about generics in the context of arrays, you learn how to use the SafeVarargs annotation type. 194 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Collections and the Need for Type Safety Java’s Collections Framework makes it possible to store objects in various kinds of object containers (known as collections) and later retrieve those objects. For example, you can store objects in a list, a set, or a map. You can then retrieve a single object, or iterate over the collection and retrieve all objects. Before Java 5 overhauled the Collections Framework to take advantage of generics, there was no way to prevent a collection from containing objects of mixed types. The compiler did not check an object’s type to see if it was suitable before it was added to a collection, and this lack of static type checking led to ClassCastExceptions. Listing 3-50 demonstrates how easy it is to generate a ClassCastException. Listing 3-50. Lack of type safety leading to a ClassCastException at runtime import java.util.ArrayList; import java.util.Iterator; import java.util.List; class Employee { private String name; Employee(String name) { this.name = name; } String getName() { return name; } } class TypeSafety { public static void main(String[] args) { List employees = new ArrayList(); employees.add(new Employee("John Doe")); employees.add(new Employee("Jane Smith")); employees.add("Jack Frost"); Iterator iter = employees.iterator(); while (iter.hasNext()) { Employee emp = (Employee) iter.next(); System.out.println(emp.getName()); } } } Listing 3-50’s main() method first instantiates java.util.ArrayList, and then uses this list collection object’s reference to add a pair of Employee objects to the list. It then adds a String object, which violates the implied contract that ArrayList is supposed to store only Employee objects. Moving on, main() obtains a java.util.Iterator instance for iterating over the list of Employees. As long as Iterator’s hasNext() method returns true, its next() method is called to return an object stored in the array list. 195 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES The Object that next() returns must be downcast to Employee so that the Employee object’s getName() method can be called to return the employee’s name. The string that this method returns is then output to the standard output device via System.out.println(). The (Employee) cast checks the type of each object returned by next() to make sure that it is Employee. Although this is true of the first two objects, it is not true of the third object. Attempting to cast "Jack Frost" to Employee results in a ClassCastException. The ClassCastException occurs because of an assumption that a list is homogenous. In other words, a list stores only objects of a single type or a family of related types. In reality, the list is heterogeneous in that it can store any Object. Listing 3-51’s generics-based homogenous list avoids ClassCastException. Listing 3-51. Lack of type safety leading to a compiler error import java.util.ArrayList; import java.util.Iterator; import java.util.List; class Employee { private String name; Employee(String name) { this.name = name; } String getName() { return name; } } class TypeSafety { public static void main(String[] args) { List<Employee> employees = new ArrayList<Employee>(); employees.add(new Employee("John Doe")); employees.add(new Employee("Jane Smith")); employees.add("Jack Frost"); Iterator<Employee> iter = employees.iterator(); while (iter.hasNext()) { Employee emp = iter.next(); System.out.println(emp.getName()); } } } Listing 3-51’s refactored main() method illustrates the central feature of generics, which is the parameterized type (a class or interface name followed by an angle bracket-delimited type list identifying what kinds of objects are legal in that context). For example, List<Employee> indicates only Employee objects can be stored in the List. As shown, the <Employee> designation can be repeated with ArrayList, as in Arraylist<Employee>, which is the collection implementation that stores the Employees. Because the compiler can figure out this type 196 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES argument from the context, you can omit the redundant Employee type name from between ArrayList’s < and > characters, resulting in List<Employee> employees = new ArrayList<>();. ■ Note Because of its appearance, many developers refer to the <> character sequence as the diamond operator. I don’t regard <> as a true operator, which is why I don’t include it in Table 1-3’s list of Java operators. Also, Iterator<Employee>—you cannot use the diamond operator in this context—indicates that iterator() returns an Iterator whose next() method returns only Employee objects. It is not necessary to cast iter.next()’s returned value to Employee because the compiler inserts the cast on your behalf. If you attempt to compile this listing, the compiler will report an error when it encounters employees.add("Jack Frost");. The error message will tell you that the compiler cannot find an add(java.lang.String) method in the java.util.List<Employee> interface. Unlike in the pre-generics List interface, which declares an add(Object) method, the generified List interface’s add() method parameter reflects the interface’s parameterized type name. For example, List<Employee> implies add(Employee). Listing 3-50 revealed that the unsafe code causing the ClassCastException (employees.add("Jack Frost");) and the code that triggers the exception ((Employee) iter.next()) are quite close. However, they are often farther apart in larger applications. Rather than having to deal with angry clients while hunting down the unsafe code that ultimately led to the ClassCastException, you can rely on the compiler saving you this frustration and effort by reporting an error when it detects this code during compilation. Detecting type safety violations at compile time is the benefit of using generics. Generic Types A generic type is a class or interface that introduces a family of parameterized types by declaring a formal type parameter list (a comma-separated list of type parameter names between angle brackets). This syntax is expressed as follows: class identifier<formal_type_parameter_list> {} interface identifier<formal_type_parameter_list> {} For example, List<E> is a generic type, where List is an interface and type parameter E identifies the list’s element type. Similarly, Map<K, V> is a generic type, where Map is an interface and type parameters K and V identify the map’s key and value types. ■ Note When declaring a generic type, it is conventional to specify single uppercase letters as type parameter names. Furthermore, these names should be meaningful. For example, E indicates element, T indicates type, K indicates key, and V indicates value. If possible, you should avoid choosing a type parameter name that is meaningless where it is used. For example, List<E> means list of elements, but what does List<S> mean? 197 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Parameterized types instantiate generic types. Each parameterized type replaces the generic type’s type parameters with type names. For example, List<Employee> (List of Employee) and List<String> (List of String) are examples of parameterized types based on List<E>. Similarly, Map<String, Employee> is an example of a parameterized type based on Map<K, V>. The type name that replaces a type parameter is known as an actual type argument. Generics supports five kinds of actual type arguments: • Concrete type: The name of a class or interface is passed to the type parameter. For example, List<Employee> employees; specifies that the list elements are Employee instances. • Concrete parameterized type: The name of a parameterized type is passed to the type parameter. For example, List<List<String>> nameLists; specifies that the list elements are lists of strings. • Array type: An array is passed to the type parameter. For example, List<String[]> countries; specifies that the list elements are arrays of Strings, possibly city names. • Type parameter: A type parameter is passed to the type parameter. For example, given class declaration class X<E> { List<E> queue; }, X’s type parameter E is passed to List’s type parameter E. • Wildcard: The ? is passed to the type parameter, indicating an unknown actual type argument. For example, List<?> list; specifies that the list elements are unknown. You will learn about wildcards later in the chapter. A generic type also identifies a raw type, which is a generic type without its type parameters. For example, List<Employee>’s raw type is List. Raw types are nongeneric and can hold any Object. ■ Note Java allows raw types to be intermixed with generic types to support the vast amount of legacy code that was written prior to the arrival of generics. However, the compiler outputs a warning message whenever it encounters a raw type in source code. Declaring and Using Your Own Generic Types It is not difficult to declare your own generic types. In addition to specifying a formal type parameter list, your generic type specifies its type parameter(s) throughout its implementation. For example, Listing 352 declares a Queue<E> generic type. Listing 3-52. Declaring and using a Queue<E> generic type class Queue<E> { private E[] elements; private int head, tail; @SuppressWarnings("unchecked") Queue(int size) 198 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES { This book was purchased by [email protected] if (size < 2) throw new IllegalArgumentException(""+size); elements = (E[]) new Object[size]; head = 0; tail = 0; } void insert(E element) throws QueueFullException { if (isFull()) throw new QueueFullException(); elements[tail] = element; tail = (tail+1)%elements.length; } E remove() throws QueueEmptyException { if (isEmpty()) throw new QueueEmptyException(); E element = elements[head]; head = (head+1)%elements.length; return element; } boolean isEmpty() { return head == tail; } boolean isFull() { return (tail+1)%elements.length == head; } public static void main(String[] args) throws QueueFullException, QueueEmptyException { Queue<String> queue = new Queue<>(6); System.out.println("Empty: "+queue.isEmpty()); System.out.println("Full: "+queue.isFull()); System.out.println("Adding A"); queue.insert("A"); System.out.println("Adding B"); queue.insert("B"); System.out.println("Adding C"); queue.insert("C"); System.out.println("Adding D"); queue.insert("D"); System.out.println("Adding E"); queue.insert("E"); System.out.println("Empty: "+queue.isEmpty()); System.out.println("Full: "+queue.isFull()); System.out.println("Removing "+queue.remove()); System.out.println("Empty: "+queue.isEmpty()); System.out.println("Full: "+queue.isFull()); System.out.println("Adding F"); 199 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES queue.insert("F"); while (!queue.isEmpty()) System.out.println("Removing "+queue.remove()); System.out.println("Empty: "+queue.isEmpty()); System.out.println("Full: "+queue.isFull()); } } class QueueEmptyException extends Exception { } class QueueFullException extends Exception { } Listing 3-52 declares Queue, QueueEmptyException, and QueueFullException classes. The latter two classes describe checked exceptions that are thrown from methods of the former class. Queue implements a queue, a data structure that stores elements in first-in, first-out order. An element is inserted at the tail and removed at the head. The queue is empty when the head equals the tail, and full when the tail is one less than the head. As a result, a queue of size n can store a maximum of n-1 elements. Notice that Queue<E>’s E type parameter appears throughout the source code. For example, E appears in the elements array declaration to denote the array’s element type. E is also specified as the type of insert()’s parameter and as remove()’s return type. E also appears in elements = (E[]) new Object[size];. (I will explain later why I specified this expression instead of specifying the more compact elements = new E[size]; expression.) The E[] cast results in the compiler warning about this cast being unchecked. The compiler is concerned that downcasting from Object[] to E[] might result in a violation of type safety because any kind of object can be stored in Object[]. The compiler’s concern is not justified in this example. There is no way that a non-E object can appear in the E[] array. Because the warning is meaningless in this context, it is suppressed by prefixing the constructor with @SuppressWarnings("unchecked"). ■ Caution Be careful when suppressing an unchecked warning. You must first prove that a ClassCastException cannot occur, and then you can suppress the warning. When you run this application, it generates the following output: Empty: true Full: false Adding A Adding B Adding C Adding D Adding E Empty: false Full: true Removing A Empty: false 200 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Full: false Adding F Removing B Removing C Removing D Removing E Removing F Empty: true Full: false Type Parameter Bounds List<E>’s E type parameter and Map<K, V>’s K and V type parameters are examples of unbounded type parameters. You can pass any actual type argument to an unbounded type parameter. It is sometimes necessary to restrict the kinds of actual type arguments that can be passed to a type parameter. For example, you might want to declare a class whose instances can only store instances of classes that subclass an abstract Shape class (such as Circle and Rectangle). To restrict actual type arguments, you can specify an upper bound, a type that serves as an upper limit on the types that can be chosen as actual type arguments. The upper bound is specified via reserved word extends followed by a type name. For example, ShapesList<E extends Shape> identifies Shape as an upper bound. You can specify ShapesList<Circle>, ShapesList<Rectangle>, and even ShapesList<Shape>, but not ShapesList<String> because String is not a subclass of Shape. You can assign more than one upper bound to a type parameter, where the first bound is a class or interface, and where each additional upper bound is an interface, by using the ampersand character (&) to separate bound names. Consider Listing 3-53. Listing 3-53. Assigning multiple upper bounds to a type parameter abstract class Shape { } class Circle extends Shape implements Comparable<Circle> { private double x, y, radius; Circle(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; } @Override public int compareTo(Circle circle) { if (radius < circle.radius) return -1; else if (radius > circle.radius) return 1; else 201 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES return 0; } @Override public String toString() { return "("+x+", "+y+", "+radius+")"; } } class SortedShapesList<S extends Shape&Comparable<S>> { @SuppressWarnings("unchecked") private S[] shapes = (S[]) new Shape[2]; private int index = 0; void add(S shape) { shapes[index++] = shape; if (index < 2) return; System.out.println("Before sort: "+this); sort(); System.out.println("After sort: "+this); } private void sort() { if (index == 1) return; if (shapes[0].compareTo(shapes[1]) > 0) { S shape = (S) shapes[0]; shapes[0] = shapes[1]; shapes[1] = shape; } } @Override public String toString() { return shapes[0].toString()+" "+shapes[1].toString(); } } class SortedShapesListDemo { public static void main(String[] args) { SortedShapesList<Circle> ssl = new SortedShapesList<>(); ssl.add(new Circle(100, 200, 300)); ssl.add(new Circle(10, 20, 30)); } } Listing 3-53’s Circle class extends Shape and implements the java.lang.Comparable interface, which is used to specify the natural ordering of Circle objects. The interface’s compareTo() method implements this ordering by returning a value to reflect the order: 202 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES • A negative value is returned if the current object should precede the object passed to compareTo() in some fashion. • A zero value is returned if the current and argument objects are the same. • A positive value is returned if the current object should succeed the argument object. Circle’s overriding compareTo() method compares two Circle objects based on their radii. This method orders a Circle instance with the smaller radius before a Circle instance with a larger radius. The SortedShapesList class specifies <S extends Shape&Comparable<S>> as its parameter list. The actual type argument passed to the S parameter must subclass Shape, and it must also implement the Comparable interface. ■ Note A type parameter bound that includes the type parameter is known as a recursive type bound. For example, Comparable<S> in S extends Shape&Comparable<S> is a recursive type bound. Recursive type bounds are rare and typically show up in conjunction with the Comparable interface, for specifying a type’s natural ordering. Circle satisfies both criteria: it subclasses Shape and implements Comparable. As a result, the compiler does not report an error when it encounters the main() method’s SortedShapesList<Circle> ssl = new SortedShapesList<>(); statement. An upper bound offers extra static type checking that guarantees that a parameterized type adheres to its bounds. This assurance means that the upper bound’s methods can be called safely. For example, sort() can call Comparable’s compareTo() method. If you run this application, you will discover the following output, which shows that the two Circle objects are sorted in ascending order of radius: Before sort: (100.0, 200.0, 300.0) (10.0, 20.0, 30.0) After sort: (10.0, 20.0, 30.0) (100.0, 200.0, 300.0) ■ Note Type parameters cannot have lower bounds. Angelika Langer explains the rationale for this restriction in her “Java Generics FAQs” (see http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107). Type Parameter Scope A type parameter’s scope (visibility) is its generic type except where masked (hidden). This scope includes the formal type parameter list of which the type parameter is a member. For example, the scope of S in SortedShapesList<S extends Shape&Comparable<S>> is all of SortedShapesList and the formal type parameter list. 203 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES It is possible to mask a type parameter by declaring a same-named type parameter in a nested type’s formal type parameter list. For example, Listing 3-54 masks an enclosing class’s T type parameter. Listing 3-54. Masking a type variable class EnclosingClass<T> { static class EnclosedClass<T extends Comparable<T>> { } } EnclosingClass’s T type parameter is masked by EnclosedClass’s T type parameter, which specifies an upper bound where only those types that implement the Comparable interface can be passed to EnclosedClass. Referencing T from within EnclosedClass refers to the bounded T and not the unbounded T passed to EnclosingClass. If masking is undesirable, it is best to choose a different name for the type parameter. For example, you might specify EnclosedClass<U extends Comparable<U>>. Although U is not as meaningful a name as T, this situation justifies this choice. The Need for Wildcards Suppose that you have created a List of String and want to output this list. Because you might create a List of Employee and other kinds of lists, you want this method to output an arbitrary List of Object. You end up creating Listing 3-55. Listing 3-55. Attempting to output a List of Object import java.util.ArrayList; import java.util.List; class OutputList { public static void main(String[] args) { List<String> ls = new ArrayList<>(); ls.add("first"); ls.add("second"); ls.add("third"); outputList(ls); } static void outputList(List<Object> list) { for (int i = 0; i < list.size(); i++) System.out.println(list.get(i)); } } Now that you’ve accomplished your objective (or so you think), you compile Listing 3-55 via javac OutputList.java. Much to your surprise, you receive the following error message: 204 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES OutputList.java:12: error: method outputList in class OutputList cannot be applied to given types; outputList(ls); ^ required: List<Object> found: List<String> reason: actual argument List<String> cannot be converted to List<Object> by method invocation conversion 1 error This error message results from being unaware of the fundamental rule of generic types: for a given subtype x of type y, and given G as a raw type declaration, G<x> is not a subtype of G<y>. To understand this rule, you must refresh your understanding of subtype polymorphism (see Chapter 2). Basically, a subtype is a specialized kind of supertype. For example, Circle is a specialized kind of Shape and String is a specialized kind of Object. This polymorphic behavior also applies to related parameterized types with the same type parameters (e.g., List<Object> is a specialized kind of java.util.Collection<Object>). However, this polymorphic behavior does not apply to multiple parameterized types that differ only in regard to one type parameter being a subtype of another type parameter. For example, List<String> is not a specialized kind of List<Object>. The following example reveals why parameterized types differing only in type parameters are not polymorphic: List<String> ls = new ArrayList<>(); List<Object> lo = ls; lo.add(new Employee()); String s = ls.get(0); This example will not compile because it violates type safety. If it compiled, a ClassCastException would be thrown at runtime because of the implicit cast to String on the final line. The first line instantiates a List of String and the second line upcasts its reference to a List of Object. The third line adds a new Employee object to the List of Object. The fourth line obtains the Employee object via get() and attempts to assign it to the List of String reference variable. However, ClassCastException is thrown because of the implicit cast to String—an Employee is not a String. ■ Note Although you cannot upcast List<String> to List<Object>, you can upcast List<String> to the raw type List in order to interoperate with legacy code. The aforementioned error message reveals that List of String is not also List of Object. To call Listing 3-55’s outputList() method without violating type safety, you can only pass an argument of List<Object> type, which limits the usefulness of this method. However, generics offer a solution: the wildcard argument (?), which stands for any type. By changing outputList()’s parameter type from List<Object> to List<?>, you can call outputList() with a List of String, a List of Employee, and so on. 205 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Generic Methods Suppose you need a method to copy a List of any kind of object to another List. Although you might consider coding a void copyList(List<Object> src, List<Object> dest) method, this method would have limited usefulness because it could only copy lists whose element type is Object. You couldn’t copy a List<Employee>, for example. If you want to pass source and destination lists whose elements are of arbitrary type (but their element types agree), you need to specify the wildcard character as a placeholder for that type. For example, you might consider writing the following copyList() class method that accepts collections of arbitrary-typed objects as its arguments: static void copyList(List<?> src, List<?> dest) { for (int i = 0; i < src.size(); i++) dest.add(src.get(i)); } This method’s parameter list is correct, but there is another problem: the compiler outputs the following error message when it encounters dest.add(src.get(i));: CopyList.java:18: error: no suitable method found for add(Object) dest.add(src.get(i)); ^ method List.add(int,CAP#1) is not applicable (actual and formal argument lists differ in length) method List.add(CAP#1) is not applicable (actual argument Object cannot be converted to CAP#1 by method invocation conversion) where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ? 1 error This error message assumes that copyList() is part of a class named CopyList. Although it appears to be incomprehensible, the message basically means that the dest.add(src.get(i)) method call violates type safety. Because ? implies that any type of object can serve as a list’s element type, it’s possible that the destination list’s element type is incompatible with the source list’s element type. For example, suppose you create a List of String as the source list and a List of Employee as the destination list. Attempting to add the source list’s String elements to the destination list, which expects Employees, violates type safety. If this copy operation were allowed, a ClassCastException instance would be thrown when trying to obtain the destination list’s elements. You could avoid this problem by specifying void copyList(List<String> src, List<String> dest), but this method header limits you to copying only lists of String objects. Alternatively, you might restrict the wildcard argument, which is demonstrated here: static void copyList(List<? extends String> src, List<? super String> dest) { for (int i = 0; i < src.size(); i++) dest.add(src.get(i)); } This method demonstrates a feature of the wildcard argument: You can supply an upper bound or (unlike with a type parameter) a lower bound to limit the types that can be passed as actual type 206 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES arguments to the generic type. Specify an upper bound via extends followed by the upper bound type after the ?, and a lower bound via super followed by the lower bound type after the ?. You interpret ? extends String to mean that any actual type argument that is String or a subclass can be passed, and you interpret ? super String to imply that any actual type argument that is String or a superclass can be passed. Because String cannot be subclassed, this means that you can only pass source lists of String and destination lists of String or Object. The problem of copying lists of arbitrary element types to other lists can be solved through the use of a generic method (a class or instance method with a type-generalized implementation). Generic methods are syntactically expressed as follows: <formal_type_parameter_list> return_type identifier(parameter_list) The formal_type_parameter_list is the same as when specifying a generic type: it consists of type parameters with optional bounds. A type parameter can appear as the method’s return_type, and type parameters can appear in the parameter_list. The compiler infers the actual type arguments from the context in which the method is invoked. You’ll discover many examples of generic methods in the Collections Framework. For example, its java.util.Collections class provides a public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) method for returning the minimum element in the given Collection according to the ordering specified by the supplied java.util.Comparator instance. You can easily convert copyList() into a generic method by prefixing the return type with <T> and replacing each wildcard with T. The resulting method header is <T> void copyList(List<T> src, List<T> dest), and Listing 3-56 presents its source code as part of an application that copies a List of Circle to another List of Circle. Listing 3-56. Declaring and using a copyList() generic method import java.util.ArrayList; import java.util.List; class Circle { private double x, y, radius; Circle(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; } @Override public String toString() { return "("+x+", "+y+", "+radius+")"; } } class CopyList { public static void main(String[] args) { List<String> ls = new ArrayList<String>(); ls.add("A"); ls.add("B"); 207 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES ls.add("C"); outputList(ls); List<String> lsCopy = new ArrayList<String>(); copyList(ls, lsCopy); outputList(lsCopy); List<Circle> lc = new ArrayList<Circle>(); lc.add(new Circle(10.0, 20.0, 30.0)); lc.add(new Circle (5.0, 4.0, 16.0)); outputList(lc); List<Circle> lcCopy = new ArrayList<Circle>(); copyList(lc, lcCopy); outputList(lcCopy); } static <T> void copyList(List<T> src, List<T> dest) { for (int i = 0; i < src.size(); i++) dest.add(src.get(i)); } static void outputList(List<?> list) { for (int i = 0; i < list.size(); i++) System.out.println(list.get(i)); System.out.println(); } } The generic method’s type parameters are inferred from the context in which the method was invoked. For example, the compiler determines that copyList(ls, lsCopy); copies a List of String to another List of String. Similarly, it determines that copyList(lc, lcCopy); copies a List of Circle to another List of Circle. When you run this application, it generates the following output: A B C A B C (10.0, 20.0, 30.0) (5.0, 4.0, 16.0) (10.0, 20.0, 30.0) (5.0, 4.0, 16.0) Arrays and Generics After presenting Listing 3-52’s Queue<E> generic type, I mentioned that I would explain why I specified elements = (E[]) new Object[size]; instead of the more compact elements = new E[size]; expression. Because of Java’s generics implementation, it isn’t possible to specify array-creation expressions that involve type parameters (e.g., new E[size] or new List<E>[50]) or actual type 208 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES arguments (e.g., new Queue<String>[15]). If you attempt to do so, the compiler will report a generic array creation error message. Before I present an example that demonstrates why allowing array-creation expressions that involve type parameters or actual type arguments is dangerous, you need to understand reification and covariance in the context of arrays, and erasure, which is at the heart of how generics are implemented. Reification is representing the abstract as if it was concrete —for example, making a memory address available for direct manipulation by other language constructs. Java arrays are reified in that they’re aware of their element types (an element type is stored internally) and can enforce these types at runtime. Attempting to store an invalid element in an array causes the JVM to throw an instance of the java.lang.ArrayStoreException class. Listing 3-57 teaches you how array manipulation can lead to an ArrayStoreException: This book was purchased by [email protected] Listing 3-57. How an ArrayStoreException arises class Point { int x, y; } class ColoredPoint extends Point { int color; } class ReificationDemo { public static void main(String[] args) { ColoredPoint[] cptArray = new ColoredPoint[1]; Point[] ptArray = cptArray; ptArray[0] = new Point(); } } Listing 3-57’s main() method first instantiates a ColoredPoint array that can store one element. In contrast to this legal assignment (the types are compatible), specifying ColoredPoint[] cptArray = new Point[1]; is illegal (and won’t compile) because it would result in a ClassCastException at runtime—the array knows that the assignment is illegal. ■ Note If it’s not obvious, ColoredPoint[] cptArray = new Point[1]; is illegal because Point instances have fewer members (only x and y) than ColoredPoint instances (x, y, and color). Attempting to access a Point instance’s nonexistent color field from its entry in the ColoredPoint array would result in a memory violation (because no memory has been assigned to color) and ultimately crash the JVM. The second line (Point[] ptArray = cptArray;) is legal because of covariance (an array of supertype references is a supertype of an array of subtype references). In this case, an array of Point references is a supertype of an array of ColoredPoint references. The nonarray analogy is that a subtype is also a supertype. For example, a Throwable instance is a kind of Object instance. 209 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Covariance is dangerous when abused. For example, the third line (ptArray[0] = new Point();) results in ArrayStoreException at runtime because a Point instance is not a ColoredPoint instance. Without this exception, an attempt to access the nonexistent member color crashes the JVM. Unlike with arrays, a generic type’s type parameters are not reified. They’re not available at runtime because they’re thrown away after the source code is compiled. This “throwing away of type parameters” is a result of erasure, which also involves inserting casts to appropriate types when the code isn’t type correct, and replacing type parameters by their upper bounds (such as Object). ■ Note The compiler performs erasure to let generic code interoperate with legacy (nongeneric) code. It transforms generic source code into nongeneric runtime code. One consequence of erasure is that you cannot use the instanceof operator with parameterized types apart from unbounded wildcard types. For example, it’s illegal to specify List<Employee> le = null; if (le instanceof ArrayList<Employee>) {}. Instead, you must change the instanceof expression to le instanceof ArrayList<?> (unbounded wildcard) or le instanceof ArrayList (raw type, which is the preferred use). Suppose you could specify an array-creation expression involving a type parameter or an actual type argument. Why would this be bad? For an answer, consider the following example, which should generate an ArrayStoreException instead of a ClassCastException but doesn’t do so: List<Employee>[] empListArray = new List<Employee>[1]; List<String> strList = new ArrayList<>(); strList.add("string"); Object[] objArray = empListArray; objArray[0] = strList; Employee e = empListArray[0].get(0); Let’s assume that the first line, which creates a one-element array where this element stores a List of Employee, is legal. The second line creates a List of String, and the third line stores a single String object in this list. The fourth line assigns empListArray to objArray. This assignment is legal because arrays are covariant and erasure converts List<Employee>[] to the List runtime type, and List subtypes Object. Because of erasure, the JVM doesn’t throw ArrayStoreException when it encounters objArray[0] = strList;. After all, we’re assigning a List reference to a List[] array at runtime. However, this exception would be thrown if generic types were reified because we’d then be assigning a List<String> reference to a List<Employee>[] array. However, there is a problem. A List<String> instance has been stored in an array that can only hold List<Employee> instances. When the compiler-inserted cast operator attempts to cast empListArray[0].get(0)’s return value ("string") to Employee, the cast operator throws a ClassCastException object. Perhaps a future version of Java will reify type parameters, making it possible to specify arraycreation expressions that involve type parameters or actual type arguments. 210 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Varargs and Generics When you invoke a varargs (variable number of arguments) method whose parameter is declared to be a parameterized type (as in List<String>), the compiler emits a warning message at the point of call. This message can be confusing and tends to discourage the use of varargs in third-party APIs. The warning message is related to heap pollution, which occurs when a variable of a parameterized type refers to an object that is not of that parameterized type. Heap pollution can only occur when an application performs an operation that would give rise to an unchecked warning at compile time. (The Java Language Specification, Third Edition discusses the concept of heap pollution [http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.12.2.1]). Unchecked warnings occur in calls to varargs methods whose parameter types are not reifiable. In other words, the parameter’s type information cannot be completely expressed at runtime because of erasure. Varargs are implemented via arrays and arrays are reified. In other words, an array’s element type is stored internally and used when required for various runtime type checks. However, this stored type information cannot include information required to represent a parameterized type that is nonreifiable. This mismatch between a reified array passing nonreified (and nonreifiable) parameterized types to a method is at the heart of the unchecked warning when the method is called. In Java 5, calling one of these methods causes a compile-time warning; declaring such a method doesn’t result in a similar warning. Although the existence of such a varargs method doesn’t cause heap pollution, its existence contributes to heap pollution by offering an easy way to cause heap pollution to occur. Furthermore, it influences heap pollution by offering the method to be called. For this reason, method declarations that contribute to heap pollution deserve a compiler warning, just as this warning is already present for method calls that cause heap pollution. The Java 7 compiler outputs warnings in both locations, and Listing 3-58 presents a scenario that leads to these warnings. Listing 3-58. Merging a variable number of Lists of Strings import java.util.ArrayList; import java.util.List; class SafeVarargsDemo { public static void main(String[] args) { List<String> list1 = new ArrayList<>(); list1.add("A"); list1.add("B"); List<String> list2 = new ArrayList<>(); list2.add("C"); list2.add("D"); list2.add("E"); System.out.println(merge(list1, list2)); // Output: [A, B, C, D, E] } //@SafeVarargs static List<String> merge(List<String>... lists) { List<String> mergedLists = new ArrayList<>(); for (int i = 0; i < lists.length; i++) mergedLists.addAll(lists[i]); 211 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES return mergedLists; } } Listing 3-58 declares a merge() method whose purpose is to merge a variable number of List of String arguments into a single List of String that this method returns. Because erasure converts the method’s List<String> parameter type to List, there is a potential for this array parameter to refer to a List that doesn’t store String objects, which is an example of heap pollution. For this reason, the compiler emits the following warnings when you compile Listing 3-58 via javac -Xlint:unchecked SafeVarargsDemo.java: SafeVarargsDemo.java:15: warning: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[] System.out.println(merge(list1, list2)); // Output: [A, B, C, D, E] ^ SafeVarargsDemo.java:18: warning: [unchecked] Possible heap pollution from parameterized vararg type List<String> static List<String> merge(List<String>... lists) ^ 2 warnings The merge() method does nothing that can lead to a ClassCastException. Therefore, these warning messages are spurious and can be ignored by annotating merge() with @SafeVarargs to assert that the body of the merge() method does not perform potentially unsafe operations on its varargs parameter. Uncomment //@SafeVarargs in Listing 3-58 and recompile. You’ll discover that these warning messages disappear. ■ Note Various standard class library methods, such as the Arrays class’s public static <T> List<T> asList(T... a) method, are annotated @SafeVarargs because they don’t throw ClassCastExceptions when their varargs array arguments are created by the compiler using proper type inference. Enums An enumerated type is a type that specifies a named sequence of related constants as its legal values. The months in a calendar, the coins in a currency, and the days of the week are examples of enumerated types. Java developers have traditionally used sets of named integer constants to represent enumerated types. Because this form of representation has proven to be problematic, Java 5 introduced the enum alternative. This section introduces you to enums. After discussing the problems with traditional enumerated types, the section presents the enum alternative. It then introduces you to the Enum class, from which enums originate. 212 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES The Trouble with Traditional Enumerated Types Listing 3-59 declares a Coin enumerated type whose set of constants identifies different kinds of coins in a currency. Listing 3-59. An enumerated type identifying coins class Coin { final static final static final static final static } int int int int PENNY = 0; NICKEL = 1; DIME = 2; QUARTER = 3; Listing 3-60 declares a Weekday enumerated type whose set of constants identifies the days of the week. Listing 3-60. An enumerated type identifying weekdays class Weekday { final static final static final static final static final static final static final static } int int int int int int int SUNDAY = 0; MONDAY = 1; TUESDAY = 2; WEDNESDAY = 3; THURSDAY = 4; FRIDAY = 5; SATURDAY = 6; Listing 3-59’s and 3-60’s approach to representing an enumerated type is problematic, where the biggest problem is the lack of compile-time type safety. For example, you can pass a coin to a method that requires a weekday and the compiler will not complain. You can also compare coins to weekdays, as in Coin.NICKEL == Weekday.MONDAY, and specify even more meaningless expressions, such as Coin.DIME+Weekday.FRIDAY-1/Coin.QUARTER. The compiler does not complain because it only sees ints. Applications that depend upon enumerated types are brittle. Because the type’s constants are compiled into an application’s classfiles, changing a constant’s int value requires you to recompile dependent applications or risk them behaving erratically. Another problem with enumerated types is that int constants cannot be translated into meaningful string descriptions. For example, what does 4 mean when debugging a faulty application? Being able to see THURSDAY instead of 4 would be more helpful. ■ Note You could circumvent the previous problem by using String constants. For example, you might specify final static String THURSDAY = "THURSDAY";. Although the constant value is more meaningful, Stringbased constants can impact performance because you cannot use == to efficiently compare just any old strings (as 213 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES you will discover in Chapter 4). Other problems related to String-based constants include hard-coding the constant’s value ("THURSDAY") instead of the constant’s name (THURSDAY) into source code, which makes it difficult to change the constant’s value at a later time; and misspelling a hard-coded constant ("THURZDAY"), which compiles correctly but is problematic at runtime. The Enum Alternative Java 5 introduced enums as a better alternative to traditional enumerated types. An enum is an enumerated type that is expressed via reserved word enum. The following example uses enum to declare Listing 3-59’s and 3-60’s enumerated types: enum Coin { PENNY, NICKEL, DIME, QUARTER } enum Weekday { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } Despite their similarity to the int-based enumerated types found in C++ and other languages, this example’s enums are classes. Each constant is a public static final field that represents an instance of its enum class. Because constants are final, and because you cannot call an enum’s constructors to create more constants, you can use == to compare constants efficiently and (unlike string constant comparisons) safely. For example, you can specify c == Coin.NICKEL. Enums promote compile-time type safety by preventing you from comparing constants in different enums. For example, the compiler will report an error when it encounters Coin.PENNY == Weekday.SUNDAY. The compiler also frowns upon passing a constant of the wrong enum kind to a method. For example, you cannot pass Weekday.FRIDAY to a method whose parameter type is Coin. Applications depending upon enums are not brittle because the enum’s constants are not compiled into an application’s classfiles. Also, the enum provides a toString() method for returning a more useful description of a constant’s value. Because enums are so useful, Java 5 enhanced the switch statement to support them. Listing 3-61 demonstrates this statement switching on one of the constants in the previous example’s Coin enum. Listing 3-61. Using the switch statement with an enum class EnhancedSwitch { enum Coin { PENNY, NICKEL, DIME, QUARTER } public static void main(String[] args) { Coin coin = Coin.NICKEL; switch (coin) { case PENNY : System.out.println("1 cent"); break; case NICKEL : System.out.println("5 cents"); break; case DIME : System.out.println("10 cents"); break; case QUARTER: System.out.println("25 cents"); break; default : assert false; } } 214 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES } Listing 3-61 demonstrates switching on an enum’s constants. This enhanced statement only allows you to specify the name of a constant as a case label. If you prefix the name with the enum, as in case Coin.DIME, the compiler reports an error. Enhancing an Enum You can add fields, constructors, and methods to an enum – you can even have the enum implement interfaces. For example, Listing 3-62 adds a field, a constructor, and two methods to Coin to associate a denomination value with a Coin constant (such as 1 for penny and 5 for nickel) and convert pennies to the denomination. Listing 3-62. Enhancing the Coin enum enum Coin { PENNY(1), NICKEL(5), DIME(10), QUARTER(25); private final int denomValue; Coin(int denomValue) { this.denomValue = denomValue; } int denomValue() { return denomValue; } int toDenomination(int numPennies) { return numPennies/denomValue; } } Listing 3-62’s constructor accepts a denomination value, which it assigns to a private blank final field named denomValue—all fields should be declared final because constants are immutable. Notice that this value is passed to each constant during its creation (PENNY(1), for example). ■ Caution When the comma-separated list of constants is followed by anything other than an enum’s closing brace, you must terminate the list with a semicolon or the compiler will report an error. Furthermore, this listing’s denomValue() method returns denomValue, and its toDenomination() method returns the number of coins of that denomination that are contained within the number of pennies passed to this method as its argument. For example, 3 nickels are contained in 16 pennies. 215 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-63 shows you how to use the enhanced Coin enum. Listing 3-63. Exercising the enhanced Coin enum class Coins { public static void main(String[] args) { if (args.length == 1) { int numPennies = Integer.parseInt(args[0]); System.out.println(numPennies+" pennies is equivalent to:"); int numQuarters = Coin.QUARTER.toDenomination(numPennies); System.out.println(numQuarters+" "+Coin.QUARTER.toString()+ (numQuarters != 1 ? "s," : ",")); numPennies -= numQuarters*Coin.QUARTER.denomValue(); int numDimes = Coin.DIME.toDenomination(numPennies); System.out.println(numDimes+" "+Coin.DIME.toString()+ (numDimes != 1 ? "s, " : ",")); numPennies -= numDimes*Coin.DIME.denomValue(); int numNickels = Coin.NICKEL.toDenomination(numPennies); System.out.println(numNickels+" "+Coin.NICKEL.toString()+ (numNickels != 1 ? "s, " : ", and")); numPennies -= numNickels*Coin.NICKEL.denomValue(); System.out.println(numPennies+" "+Coin.PENNY.toString()+ (numPennies != 1 ? "s" : "")); } System.out.println(); System.out.println("Denomination values:"); for (int i = 0; i < Coin.values().length; i++) System.out.println(Coin.values()[i].denomValue()); } } Listing 3-63 describes an application that converts its solitary “pennies” command-line argument to an equivalent amount expressed in quarters, dimes, nickels, and pennies. In addition to calling a Coin constant’s denomValue() and toDenomValue() methods, the application calls toString() to output a string representation of the coin. Another called enum method is values(). This method returns an array of all Coin constants that are declared in the Coin enum (value()’s return type, in this example, is Coin[]). This array is useful when you need to iterate over these constants. For example, Listing 3-63 calls this method to output each coin’s denomination. When you run this application with 119 as its command-line argument (java Coins 119), it generates the following output: 119 pennies is equivalent to: 4 QUARTERs, 1 DIME, 1 NICKEL, and 4 PENNYs Denomination values: 216 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES 1 5 10 25 The output shows that toString() returns a constant’s name. It is sometimes useful to override this method to return a more meaningful value. For example, a method that extracts tokens (named character sequences) from a string might use a Token enum to list token names and, via an overriding toString() method, values – see Listing 3-64. Listing 3-64. Overriding toString() to return a Token constant’s value enum Token { IDENTIFIER("ID"), INTEGER("INT"), LPAREN("("), RPAREN(")"), COMMA(","); private final String tokValue; Token(String tokValue) { this.tokValue = tokValue; } @Override public String toString() { return tokValue; } public static void main(String[] args) { System.out.println("Token values:"); for (int i = 0; i < Token.values().length; i++) System.out.println(Token.values()[i].name()+" = "+ Token.values()[i]); } } Listing 3-64’s main() method calls values() to return the array of Token constants. For each constant, it calls the constant’s name() method to return the constant’s name, and implicitly calls toString() to return the constant’s value. If you were to run this application, you would observe the following output: Token values: IDENTIFIER = ID INTEGER = INT LPAREN = ( RPAREN = ) COMMA = , Another way to enhance an enum is to assign a different behavior to each constant. You can accomplish this task by introducing an abstract method into the enum and overriding this method in an anonymous subclass of the constant. Listing 3-65’s TempConversion enum demonstrates this technique. 217 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Listing 3-65. Using anonymous subclasses to vary the behaviors of enum constants enum TempConversion { C2F("Celsius to Fahrenheit") { @Override double convert(double value) { return value*9.0/5.0+32.0; } }, F2C("Fahrenheit to Celsius") { @Override double convert(double value) { return (value-32.0)*5.0/9.0; } }; TempConversion(String desc) { this.desc = desc; } private String desc; @Override public String toString() { return desc; } abstract double convert(double value); public static void main(String[] args) { System.out.println(C2F+" for 100.0 degrees = "+C2F.convert(100.0)); System.out.println(F2C+" for 98.6 degrees = "+F2C.convert(98.6)); } } When you run this application, it generates the following output: Celsius to Fahrenheit for 100.0 degrees = 212.0 Fahrenheit to Celsius for 98.6 degrees = 37.0 The Enum Class The compiler regards enum as syntactic sugar. When it encounters an enum type declaration (enum Coin {}), it generates a class whose name (Coin) is specified by the declaration, and which also subclasses the abstract Enum class (in the java.lang package), the common base class of all Java-based enumeration types. If you examine Enum’s Java documentation, you will discover that it overrides Object’s clone(), equals(), finalize(), hashCode(), and toString() methods: 218 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via ==. • equals() is overridden to compare constants via their references—constants with the same identities (==) must have the same contents (equals()), and different identities imply different contents. • finalize() is overridden to ensure that constants cannot be finalized. • hashCode() is overridden because equals() is overridden. • toString() is overridden to return the constant’s name. This book was purchased by [email protected] Except for toString(), all of the overriding methods are declared final so that they cannot be overridden in a subclass. Enum also provides its own methods. These methods include the final compareTo(), (Enum implements Comparable), getDeclaringClass(), name(), and ordinal() methods: • compareTo() compares the current constant with the constant passed as an argument to see which constant precedes the other constant in the enum, and returns a value indicating their order. This method makes it possible to sort an array of unsorted constants. • getDeclaringClass() returns the Class object corresponding to the current constant’s enum. For example, the Class object for Coin is returned when calling Coin.PENNY.getDeclaringClass() for enum Coin { PENNY, NICKEL, DIME, QUARTER }. Also, TempConversion is returned when calling TempConversion.C2F.getDeclaringClass() for Listing 3-65’s TempConversion enum. The compareTo() method uses Class’s getClass() method and Enum’s getDeclaringClass() method to ensure that only constants belonging to the same enum are compared. Otherwise, a ClassCastException is thrown. (I will discuss Class in Chapter 4.) • name() returns the constant’s name. Unless overridden to return something more descriptive, toString() also returns the constant’s name. • ordinal() returns a zero-based ordinal, an integer that identifies the position of the constant within the enum type. compareTo() compares ordinals. Enum also provides the public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) method for returning the enum constant from the specified enum with the specified name: • enumType identifies the Class object of the enum from which to return a constant. • name identifies the name of the constant to return. For example, Coin penny = Enum.valueOf(Coin.class, "PENNY"); assigns the Coin constant whose name is PENNY to penny. You will not discover a values() method in Enum’s Java documentation because the compiler synthesizes (manufactures) this method while generating the class. 219 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES Extending the Enum Class Enum’s generic type is Enum<E extends Enum<E>>. Although the formal type parameter list looks ghastly, it is not that hard to understand. But first, take a look at Listing 3-66. Listing 3-66. The Coin class as it appears from the perspective of its classfile final class Coin extends Enum<Coin> { public static final Coin PENNY = new Coin("PENNY", 0); public static final Coin NICKEL = new Coin("NICKEL", 1); public static final Coin DIME = new Coin("DIME", 2); public static final Coin QUARTER = new Coin("QUARTER", 3); private static final Coin[] $VALUES = { PENNY, NICKEL, DIME, QUARTER }; public static Coin[] values() { return Coin.$VALUES.clone(); } public static Coin valueOf(String name) { return Enum.valueOf(Coin.class, "Coin"); } private Coin(String name, int ordinal) { super(name, ordinal); } } Behind the scenes, the compiler converts enum Coin { PENNY, NICKEL, DIME, QUARTER } into a class declaration that is similar to Listing 3-66. The following rules show you how to interpret Enum<E extends Enum<E>> in the context of Coin extends Enum<Coin>: • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum<Coin>. • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum. • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument. The third rule allows Enum to declare methods—compareTo(), getDeclaringClass(), and valueOf()— whose parameter and/or return types are specified in terms of the subclass (Coin), and not in terms of Enum. The rationale for doing this is to avoid having to specify casts. For example, you do not need to cast valueOf()’s return value to Coin in Coin penny = Enum.valueOf(Coin.class, "PENNY");. ■ Note You cannot compile Listing 3-66 because the compiler will not compile any class that extends Enum. It will also complain about super(name, ordinal);. 220 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES EXERCISES The following exercises are designed to test your understanding of nested types, packages, static imports, exceptions, assertions, annotations, generics, and enums: 1. A 2D graphics package supports two-dimensional drawing and transformations (rotation, scaling, translation, and so on). These transformations require a 3-by-3 matrix (a table). Declare a G2D class that encloses a private Matrix nonstatic member class. In addition to declaring a Matrix(int nrows, int ncols) constructor, Matrix declares a void dump() method that outputs the matrix values to standard output in a tabular format. Instantiate Matrix within G2D’s noargument constructor, and initialize the Matrix instance to the identity matrix (a matrix where all entries are 0 except for those on the upper-left to lower-right diagonal, which are 1. Then invoke this instance’s dump() method from the constructor. Include a main() method to test G2D. 2. Extend the logging package to support a null device in which messages are thrown away. 3. Continuing from Exercise 1, introduce the following matrix-multiplication method into Matrix: Matrix multiply(Matrix m) { Matrix result = new Matrix(matrix.length, matrix[0].length); for (int i = 0; i < matrix.length; i++) for (int j = 0; j < m.matrix[0].length; j++) for (int k = 0; k < m.matrix.length; k++) result.matrix[i][j] = result.matrix[i][j]+ matrix[i][k]*m.matrix[k][j]; return result; } Next, declare a void rotate(double angle) method in G2D. This method’s first task is to negate its angle argument (to ensure counterclockwise rotation), which specifies a rotation angle in degrees. It then creates a 3-by-3 rotation Matrix and initializes the following (row, column) entries: (0, 0) to the cosine of the angle, (1, 0) to the sine of the angle, (0, 1) to the negative of the angle’s sine, (1, 1) to the cosine of the angle, and (2, 2) to 1.0. Statically import all necessary Math class methods. Finally, rotate() multiplies the identity matrix created in G2D’s constructor by this rotation matrix, and invokes dump() to dump the result. Test rotate() from the main() method by executing G2D g2d = new G2D(); g2d.rotate(45);. You should observe the following output: 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.7071067811865476 0.7071067811865475 0.0 221 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES -0.7071067811865475 0.7071067811865476 0.0 0.0 0.0 1.0 4. Modify the logging package so that Logger’s connect() method throws CannotConnectException when it cannot connect to its logging destination, and the other two methods each throw NotConnectedException when connect() was not called or when it threw CannotConnectException. Modify TestLogger to respond appropriately to thrown CannotConnectException and NotConnectedException objects. 5. Continuing from Exercise 3, use an assertion to verify the class invariant that the transformation matrix is initialized to the identity matrix before G2D’s constructor ends. 6. Declare a ToDo marker annotation type that annotates only type elements, and that also uses the default retention policy. 7. Rewrite the StubFinder application to work with Listing 3-43’s Stub annotation type (with appropriate @Target and @Retention annotations) and Listing 3-44’s Deck class. 8. Implement a Stack<E> generic type in a manner that is similar to Listing 3-52’s Queue class. Stack must declare push(), pop(), and isEmpty() methods (it could also declare an isFull() method but that method is not necessary in this exercise), push() must throw a StackFullException instance when the stack is full, and pop() must throw a StackEmptyException instance when the stack is empty. (You must create your own StackFullException and StackEmptyException helper classes because they are not provided for you in Java’s standard class library.) Declare a similar main() method, and insert two assertions into this method that validate your assumptions about the stack being empty immediately after being created and immediately after popping the last element. 9. Declare a Compass enum with NORTH, SOUTH, EAST, and WEST members. Declare a UseCompass class whose main() method randomly selects one of these constants and then switches on that constant. Each of the switch statement’s cases should output a message such as heading north. Summary Java supports advanced language features related to nested types, packages, static imports, exceptions, assertions, annotations, generics, and enums. Classes that are declared outside of any class are known as top-level classes. Java also supports nested classes, which are classes declared as members of other classes or scopes, and which help you implement top-level class architecture. There are four kinds of nested classes: static member classes, nonstatic member classes, anonymous classes, and local classes. The latter three categories are known as inner classes. Java supports the partitioning of top-level types into multiple namespaces, to better organize these types and to also prevent name conflicts. Java uses packages to accomplish these tasks. 222 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES The package statement identifies the package in which a source file’s types are located. The import statement imports types from a package by telling the compiler where to look for unqualified type names during compilation. An exception is a divergence from an application’s normal behavior. Although it can be represented by an error code or object, Java uses objects because error codes are meaningless and cannot contain information about what led to the exception. Java provides a hierarchy of classes that represent different kinds of exceptions. These classes are rooted in Throwable. Moving down the throwable hierarchy, you encounter the Exception and Error classes, which represent nonerror exceptions and errors. Exception and its subclasses, except for RuntimeException (and its subclasses), describe checked exceptions. They are checked because the compiler checks the code to ensure that an exception is handled where thrown or identified as being handled elsewhere. RuntimeException and its subclasses describe unchecked exceptions. You do not have to handle these exceptions because they represent coding mistakes (fix the mistakes). Although the names of their classes can appear in throws clauses, doing so adds clutter. The throw statement throws an exception to the JVM, which searches for an appropriate handler. If the exception is checked, its name must appear in the method’s throws clause, unless the name of the exception’s superclass is listed in this clause. A method handles one or more exceptions by specifying a try statement and appropriate catch blocks. A finally block can be included to execute cleanup code whether or not an exception is thrown, and before a thrown exception leaves the method. An assertion is a statement that lets you express an assumption of application correctness via a Boolean expression. If this expression evaluates to true, execution continues with the next statement; otherwise, an error that identifies the cause of failure is thrown. There are many situations where assertions should be used. These situations organize into internal invariant, control-flow invariant, and design-by-contract categories. An invariant is something that does not change. Although there are many situations where assertions should be used, there also are situations where they should be avoided. For example, you should not use assertions to check the arguments that are passed to public methods. The compiler records assertions in the classfile. However, assertions are disabled at runtime because they can affect performance. You must enable the classfile’s assertions before you can test assumptions about the behaviors of your classes. Annotations are instances of annotation types and associate metadata with application elements. They are expressed in source code by prefixing their type names with @ symbols. For example, @Readonly is an annotation and Readonly is its type. Java supplies a wide variety of annotation types, including the compiler-oriented Override, Deprecated, SuppressWarnings, and SafeVarargs types. However, you can also declare your own annotation types by using the @interface syntax. Annotation types can be annotated with meta-annotations that identify the application elements they can target (such as constructors, methods, or fields), their retention policies, and other characteristics. Annotations whose types are assigned a runtime retention policy via @Retention annotations can be processed at runtime using custom applications or Java’s apt tool, whose functionality has been integrated into the compiler starting with Java 6. Java 5 introduced generics, language features for declaring and using type-agnostic classes and interfaces. When working with Java’s Collections Framework, these features help you avoid ClassCastExceptions. 223 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES A generic type is a class or interface that introduces a family of parameterized types by declaring a formal type parameter list. The type name that replaces a type parameter is known as an actual type argument. There are five kinds of actual type arguments: concrete type, concrete parameterized type, array type, type parameter, and wildcard. Furthermore, a generic type also identifies a raw type, which is a generic type without its type parameters. Many type parameters are unbounded in that they can accept any actual type argument. To restrict actual type arguments, you can specify an upper bound, a type that serves as an upper limit on the types that can be chosen as actual type arguments. The upper bound is specified via reserved word extends followed by a type name. However, lower bounds are not supported. A type parameter’s scope is its generic type except where masked. This scope includes the formal type parameter list of which the type parameter is a member. To preserve type safety, you are not allowed to violate the fundamental rule of generic types: for a given subtype x of type y, and given G as a raw type declaration, G<x> is not a subtype of G<y>. In other words, multiple parameterized types that differ only in regard to one type parameter being a subtype of another type parameter are not polymorphic. For example, List<String> is not a specialized kind of List<Object>. This restriction can be ameliorated without violating type safety by using wildcards. For example, where a void output(List<Object> list) method can only output a List that contains Objects (to adhere to the aforementioned rule), a void output(List<?> list) method can output a List of arbitrary objects. Wildcards alone cannot solve the problem where you want to copy one List to another. The solution is to use a generic method, a static or non-static method with a type-generalized implementation. For example, a <T> void copyList(List<T> src, List<T> dest) method can copy a source List of arbitrary objects (whose type is specified by T) to another List of arbitrary objects (having the same type). The compiler infers the actual type arguments from the context in which the method is invoked. Reification is representing the abstract as if it was concrete -- for example, making a memory address available for direct manipulation by other language constructs. Java arrays are reified in that they’re aware of their element types (an element type is stored internally) and can enforce these types at runtime. Attempting to store an invalid element in an array causes the JVM to throw an instance of the ArrayStoreException class. Unlike with arrays, a generic type’s type parameters are not reified. They’re not available at runtime because they’re thrown away after the source code is compiled. This “throwing away of type parameters” is a result of erasure, which also involves inserting casts to appropriate types when the code isn’t type correct, and replacing type parameters by their upper bounds (such as Object). When you invoke a varargs method whose parameter is declared to be a parameterized type (as in List<String>), the compiler emits a warning message at the point of call. This message can be confusing and tends to discourage the use of varargs in third-party APIs. The warning message is related to heap pollution, which occurs when a variable of a parameterized type refers to an object that is not of that parameterized type. If a varargs method is declared such that this warning message occurs, and if the varargs method doesn’t perform a potentially unsafe operation on its varargs parameter, you can annotate the method @SafeVarargs, and eliminate the warning message. An enumerated type is a type that specifies a named sequence of related constants as its legal values. Java developers have traditionally used sets of named integer constants to represent enumerated types. Because sets of named integer constants have proven to be problematic, Java 5 introduced the enum alternative. An enum is an enumerated type that is expressed via reserved word enum. 224 CHAPTER 3 EXPLORING ADVANCED LANGUAGE FEATURES You can add fields, constructors, and methods to an enum—you can even have the enum implement interfaces. Also, you can override toString() to provide a more useful description of a constant’s value, and subclass constants to assign different behaviors. The compiler regards enum as syntactic sugar for a class that subclasses Enum. This abstract class overrides various Object methods to provide default behaviors (usually for safety reasons), and provides additional methods for various purposes. This chapter largely completes our tour of the Java language. However, there are a few more advanced language features to explore. You will encounter a couple of these minor features in Chapter 4, which begins a multichapter exploration of additional types that are located in Java’s standard class library. 225 CHAPTER 4 Touring Language APIs Java’s standard class library provides various language-oriented APIs. Most of these APIs reside in the java.lang package and its subpackages, although a few APIs reside in java.math. Chapter 4 first introduces you to the java.lang/subpackage Math and StrictMath, Package, Primitive Type Wrapper Class, Reference, Reflection, String, StringBuffer and StringBuilder, System, and Threading APIs. This chapter then introduces you to java.math’s BigDecimal and BigInteger APIs. Math and StrictMath The java.lang.Math class declares double constants E and PI that represent the natural logarithm base value (2.71828...) and the ratio of a circle’s circumference to its diameter (3.14159...). E is initialized to 2.718281828459045 and PI is initialized to 3.141592653589793. Math also declares assorted class methods to perform various math operations. Table 4-1 describes many of these methods. Table 4-1. Math Methods Method Description double abs(double d) Return the absolute value of d. There are four special cases: abs(-0.0) = +0.0, abs(+infinity) = +infinity, abs(infinity) = +infinity, and abs(NaN) = NaN. float abs(float f) Return the absolute value of f. There are four special cases: abs(-0.0) = +0.0, abs(+infinity) = +infinity, abs(infinity) = +infinity, and abs(NaN) = NaN. int abs(int i) Return the absolute value of i. There is one special case: the absolute value of Integer.MIN_VALUE is Integer.MIN_VALUE. long abs(long l) Return the absolute value of l. There is one special case: the absolute value of Long.MIN_VALUE is Long.MIN_VALUE. double acos(double d) Return angle d’s arc cosine within the range 0 through PI. There are three special cases: acos(anything > 1) = NaN, acos(anything < -1) = NaN, and acos(NaN) = NaN. 227 CHAPTER 4 TOURING LANGUAGE APIS 228 double asin(double d) Return angle d’s arc sine within the range -PI/2 through PI/2. There are three special cases: asin(anything > 1) = NaN, asin(anything < -1) = NaN, and asin(NaN) = NaN. double atan(double d) Return angle d’s arc tangent within the range -PI/2 through PI/2. There are five special cases: atan(+0.0) = +0.0, atan(0.0) = -0.0, atan(+infinity) = +PI/2, atan(-infinity) = -PI/2, and atan(NaN) = NaN. double ceil(double d) Return the smallest value (closest to negative infinity) that is not less than d and is equal to an integer. There are six special cases: ceil(+0.0) = +0.0, ceil(-0.0) = -0.0, ceil(anything > -1.0 and < 0.0) = -0.0, ceil(+infinity) = +infinity, ceil(-infinity) = -infinity, and ceil(NaN) = NaN. double cos(double d) Return the cosine of angle d (expressed in radians). There are three special cases: cos(+infinity) = NaN, cos(-infinity) = NaN, and cos(NaN) = NaN. double exp(double d) Return Euler’s number e to the power d. There are three special cases: exp(+infinity) = +infinity, exp(-infinity) = +0.0, and exp(NaN) = NaN. double floor(double d) Return the largest value (closest to positive infinity) that is not greater than d and is equal to an integer. There are five special cases: floor(+0.0) = +0.0, floor(-0.0) = -0.0, floor(+infinity) = +infinity, floor(-infinity) = infinity, and floor(NaN) = NaN. double log(double d) Return the natural logarithm (base e) of d. There are six special cases: log(+0.0) = -infinity, log(-0.0) = infinity, log(anything < 0) = NaN, log(+infinity) = +infinity, log(-infinity) = NaN, and log(NaN) = NaN. double log10(double d) Return the base 10 logarithm of d. There are six special cases: log10(+0.0) = -infinity, log10(-0.0) = -infinity, log10(anything < 0) = NaN, log10(+infinity) = +infinity, log10(-infinity) = NaN, and log10(NaN) = NaN. double max(double d1, double d2) Return the most positive (closest to positive infinity) of d1 and d2. There are four special cases: max(NaN, anything) = NaN, max(anything, NaN) = NaN, max(+0.0, -0.0) = +0.0, and max(-0.0, +0.0) = +0.0. float max(float f1, float f2) Return the most positive (closest to positive infinity) of f1 and f2. There are four special cases: max(NaN, anything) = NaN, max(anything, NaN) = NaN, max(+0.0, -0.0) = +0.0, CHAPTER 4 TOURING LANGUAGE APIS and max(-0.0, +0.0) = +0.0. int max(int i1, int i2) Return the most positive (closest to positive infinity) of i1 and i2. long max(long l1, long l2) Return the most positive (closest to positive infinity) of l1 and l2. double min(double d1, double d2) Return the most negative (closest to negative infinity) of d1 and d2. There are four special cases: min(NaN, anything) = NaN, min(anything, NaN) = NaN, min(+0.0, -0.0) = -0.0, and min(-0.0, +0.0) = -0.0. float min(float f1, float f2) Return the most negative (closest to negative infinity) of f1 and f2. There are four special cases: min(NaN, anything) = NaN, min(anything, NaN) = NaN, min(+0.0, -0.0) = -0.0, and min(-0.0, +0.0) = -0.0. int min(int i1, int i2) Return the most negative (closest to negative infinity) of i1 and i2. long min(long l1, long l2) Return the most negative (closest to negative infinity) of l1 and l2. double random() Return a pseudorandom number between 0.0 (inclusive) and 1.0 (exclusive). long round(double d) Return the result of rounding d to a long integer. The result is equivalent to (long) Math.floor(d+0.5). There are seven special cases: round(+0.0) = +0.0, round(-0.0) = +0.0, round(anything > Long.MAX_VALUE) = Long.MAX_VALUE, round(anything < Long.MIN_VALUE) = Long.MIN_VALUE, round(+infinity) = Long.MAX_VALUE, round(-infinity) = Long.MIN_VALUE, and round(NaN) = +0.0. int round(float f) Return the result of rounding f to an integer. The result is equivalent to (int) Math.floor(f+0.5). There are seven special cases: round(+0.0) = +0.0, round(-0.0) = +0.0, round(anything > Integer.MAX_VALUE) = Integer.MAX_VALUE, round(anything < Integer.MIN_VALUE) = Integer.MIN_VALUE, round(+infinity) = Integer.MAX_VALUE, round(-infinity) = Integer.MIN_VALUE, and round(NaN) = +0.0. double signum(double d) Return the sign of d as -1.0 (d less than 0.0), 0.0 (d equals 0.0), and 1.0 (d greater than 0.0). There are five special cases: signum(+0.0) = +0.0, signum(-0.0) = -0.0, signum(+infinity) = +1.0, signum(-infinity) = -1.0, and 229 CHAPTER 4 TOURING LANGUAGE APIS This book was purchased by [email protected] signum(NaN) = NaN. float signum(float f) Return the sign of f as -1.0 (f less than 0.0), 0.0 (f equals 0.0), and 1.0 (f greater than 0.0). There are five special cases: signum(+0.0) = +0.0, signum(-0.0) = -0.0, signum(+infinity) = +1.0, signum(-infinity) = -1.0, and signum(NaN) = NaN. double sin(double d) Return the sine of angle d (expressed in radians). There are five special cases: sin(+0.0) = +0.0, sin(-0.0) = -0.0, sin(+infinity) = NaN, sin(-infinity) = NaN, and sin(NaN) = NaN. double sqrt(double d) Return the square root of d. There are five special cases: sqrt(+0.0) = +0.0, sqrt(-0.0) = -0.0, sqrt(anything < 0) = NaN, sqrt(+infinity) = +infinity, and sqrt(NaN) = NaN. double tan(double d) Return the tangent of angle d (expressed in radians). There are five special cases: tan(+0.0) = +0.0, tan(-0.0) = -0.0, tan(+infinity) = NaN, tan(-infinity) = NaN, and tan(NaN) = NaN. double toDegrees(double angrad) Convert angle angrad from radians to degrees via expression angrad*180/PI. There are five special cases: toDegrees(+0.0) = +0.0, toDegrees(-0.0) = -0.0, toDegrees(+infinity) = +infinity, toDegrees(-infinity) = -infinity, and toDegrees(NaN) = NaN. double toRadians(angdeg) Convert angle angdeg from degrees to radians via expression angdeg/180*PI. There are five special cases: toRadians(+0.0) = +0.0, toRadians(-0.0) = -0.0, toRadians(+infinity) = +infinity, toRadians(-infinity) = -infinity, and toRadians(NaN) = NaN. Table 4-1 reveals a wide variety of useful math-oriented methods. For example, each abs() method returns its argument’s absolute value (number without regard for sign). abs(double) and abs(float) are useful for comparing double precision floating-point and floatingpoint values safely. For example, 0.3 == 0.1+0.1+0.1 evaluates to false because 0.1 has no exact representation. However, you can compare these expressions with abs() and a tolerance value, which indicates an acceptable range of error. For example, Math.abs(0.3-(0.1+0.1+0.1)) < 0.1 returns true because the absolute difference between 0.3 and 0.1+0.1+0.1 is less than a 0.1 tolerance value. Previous chapters demonstrated other Math methods. For example, Chapter 2 demonstrated Math’s random(), sin(), cos(), and toRadians() methods. As Chapter 3’s Lotto649 application revealed, random() (which returns a number that appears to be randomly chosen but is actually chosen by a predictable math calculation, and hence is pseudorandom) is useful in simulations (as well as in games and wherever an element of chance is needed). However, its double precision floating-point range of 0.0 through (almost) 1.0 isn’t practical. To make random() more useful, its return value must be transformed into a more useful range, perhaps integer values 0 through 230 CHAPTER 4 TOURING LANGUAGE APIS 49, or maybe -100 through 100. You will find the following rnd() method useful for making these transformations: static int rnd(int limit) { return (int) (Math.random()*limit); } rnd() transforms random()’s 0.0 to (almost) 1.0 double precision floating-point range to a 0 through limit-1 integer range. For example, rnd(50) returns an integer ranging from 0 through 49. Also, 100+rnd(201) transforms 0.0 to (almost) 1.0 into -100 through 100 by adding a suitable offset and passing an appropriate limit value. ■ Caution Do not specify (int) Math.random()*limit because this expression always evaluates to 0. The expression first casts random()’s double precision floating-point fractional value (0.0 through 0.99999. . .) to integer 0 by truncating the fractional part, and then multiplies 0 by limit, resulting in 0. The sin() and cos() methods implement the sine and cosine trigonometric functions—see http://en.wikipedia.org/wiki/Trigonometric_functions. These functions have uses ranging from the study of triangles to modeling periodic phenomena (such as simple harmonic motion—see http://en.wikipedia.org/wiki/Simple_harmonic_motion). We can use sin() and cos() to generate and display sine and cosine waves. Listing 4-1 presents the source code to an application that does just this. Listing 4-1. Graphing sine and cosine waves class Graph { final static int ROWS = 11; // Must be odd final static int COLS= 23; public static void main(String[] args) { char[][] screen = new char[ROWS][]; for (int row = 0; row < ROWS; row++) screen[row] = new char[COLS]; double scaleX = COLS/360.0; for (int degree = 0; degree < 360; degree++) { int row = ROWS/2+ (int) Math.round(ROWS/2*Math.sin(Math.toRadians(degree))); int col = (int) (degree*scaleX); screen[row][col] = 'S'; row = ROWS/2+ (int) Math.round(ROWS/2*Math.cos(Math.toRadians(degree))); screen[row][col] = (screen[row][col] == 'S') ? '*' : 'C'; } for (int row = ROWS-1; row >= 0; row--) 231 CHAPTER 4 TOURING LANGUAGE APIS { for (int col = 0; col < COLS; col++) System.out.print(screen[row][col]); System.out.println(); } } } Listing 4-1 introduces a Graph class that first declares a pair of constants: NROWS and NCOLS. These constants specify the dimensions of an array on which the graphs are generated. NROWS must be assigned an odd integer; otherwise, an instance of the java.lang.ArrayIndexOutOfBoundsException class is thrown. ■ Tip It’s a good idea to use constants wherever possible. The source code is easier to maintain because you only need to change the constant’s value in one place instead of having to change each corresponding value throughout the source code. Graph next declares its main() method, which first creates a two-dimensional screen array of characters. This array is used to simulate an old-style character-based screen for viewing the graphs. main() next calculates a horizontal scale value for scaling each graph horizontally so that 360 horizontal (degree) positions fit into the number of columns specified by NCOLS. Continuing, main() enters a for loop that, for each of the sine and cosine graphs, creates (row, column) coordinates for each degree value, and assigns a character to the screen array at those coordinates. The character is S for the sine graph, C for the cosine graph, and * when the cosine graph intersects the sine graph. The row calculation invokes toRadians() to convert its degree argument to radians, which is required by the sin() and cos() methods. The value returned from sin() or cos() (-1 to 1) is then multiplied by ROWS/2 to scale this value to half the number of rows in the screen array. After rounding the result to the nearest long integer via the long round(double d) method, a cast is used to convert from long integer to integer, and this integer is added to ROW/2 to offset the row coordinate so that it’s relative to the array’s middle row. The column calculation is simpler, multiplying the degree value by the horizontal scale factor. The screen array is dumped to the standard output device via a pair of nested for loops. The outer for loop inverts the screen so that it appears right side up—row number 0 should output last. Compile Listing 4-1 (javac Graph.java) and run the application (java Graph). You observe the following output: 232 CHAPTER 4 TOURING LANGUAGE APIS CC SSSS CC CSSS SS CC S*C SS CC S CC SS CC SS CC SS CC S CC S CC S C SS C SS CC SS CC S CC SCC SS CC CSS SSS CCCCC SSSS ■ Note When I created the screen array, I took advantage of the fact that every element is initialized to 0, which is interpreted as the null character. When a System.out.print() or System.out.println() method detects this character, it outputs a space character instead. Table 4-1 also reveals a few curiosities starting with +infinity, -infinity, +0.0, -0.0, and NaN (Not a Number). Java’s floating-point calculations are capable of returning +infinity, -infinity, +0.0, -0.0, and NaN because Java largely conforms to IEEE 754 (http://en.wikipedia.org/wiki/IEEE_754), a standard for floating-point calculations. The following are the circumstances under which these special values arise: • +infinity returns from attempting to divide a positive number by 0.0. For example, System.out.println(1.0/0.0); outputs Infinity. • -infinity returns from attempting to divide a negative number by 0.0. For example, System.out.println(-1.0/0.0); outputs -Infinity. • NaN returns from attempting to divide 0.0 by 0.0, attempting to calculate the square root of a negative number, and attempting other strange operations. For example, System.out.println(0.0/0.0); and System.out.println(Math.sqrt(1.0)); each output NaN. • +0.0 results from attempting to divide a positive number by +infinity. For example, System.out.println(1.0/(1.0/0.0)); outputs +0.0. • -0.0 results from attempting to divide a negative number by +infinity. For example, System.out.println(-1.0/(1.0/0.0)); outputs -0.0. Once an operation yields +infinity, -infinity, or NaN, the rest of the expression usually equals that special value. For example, System.out.println(1.0/0.0*20.0); outputs Infinity. Also, an expression that first yields +infinity or -infinity might devolve into NaN. For example, 1.0/0.0*0.0 yields +infinity (1.0/0.0) and then NaN (+infinity*0.0). Another curiosity is Integer.MAX_VALUE, Integer.MIN_VALUE, Long.MAX_VALUE, and Long.MIN_VALUE. Each of these items is a primitive type wrapper class constant that identifies the maximum or minimum value that can be represented by the class’s associated primitive type. (I discuss primitive type wrapper classes later in this chapter.) 233 CHAPTER 4 TOURING LANGUAGE APIS Finally, you might wonder why the abs(), max(), and min() overloaded methods do not include byte and short versions, as in byte abs(byte b) and short abs(short s). There is no need for these methods because the limited ranges of bytes and short integers make them unsuitable in calculations. If you need such a method, check out Listing 4-2. Listing 4-2. Obtaining absolute values for byte integers and short integers class AbsByteShort { static byte abs(byte b) { return (b < 0) ? (byte) -b : b; } static short abs(short s) { return (s < 0) ? (short) -s : s; } public static void main(String[] args) { byte b = -2; System.out.println(abs(b)); // Output: 2 short s = -3; System.out.println(abs(s)); // Output: 3 } } Listing 4-2’s (byte) and (short) casts are necessary because -b converts b’s value from a byte to an int, and -s converts s’s value from a short to an int. In contrast, these casts are not needed with (b < 0) and (s < 0), which automatically cast b’s and s’s values to an int before comparing them with intbased 0. ■ Tip Their absence from Math suggests that byte and short are not very useful in method declarations. However, these types are useful when declaring arrays whose elements store small values (such as a binary file’s byte values). If you declared an array of int or long to store such values, you would end up wasting heap space (and might even run out of memory). While searching through the Java documentation for the java.lang package, you will probably encounter a class named StrictMath. Apart from a longer name, this class appears to be identical to Math. The differences between these classes can be summed up as follows: 234 • StrictMath’s methods return exactly the same results on all platforms. In contrast, some of Math’s methods might return values that vary ever so slightly from platform to platform. • Because StrictMath cannot utilize platform-specific features such as an extendedprecision math coprocessor, an implementation of StrictMath might be less efficient than an implementation of Math. CHAPTER 4 TOURING LANGUAGE APIS For the most part, Math’s methods call their StrictMath counterparts. Two exceptions are toDegrees() and toRadians(). Although these methods have identical code bodies in both classes, StrictMath’s implementations include reserved word strictfp in the method headers: public static strictfp double toDegrees(double angrad) public static strictfp double toRadians(double angdeg) Wikipedia’s “strictfp” entry (http://en.wikipedia.org/wiki/Strictfp) mentions that strictfp restricts floating-point calculations to ensure portability. This reserved word accomplishes portability in the context of intermediate floating-point representations and overflows/underflows (generating a value too large or small to fit a representation). ■ Note The previously cited “strictfp” article states that Math contains public static strictfp double abs(double); and other strictfp methods. If you check out this class’s source code under Java 7, you will not find strictfp anywhere in the source code. However, many Math methods (such as sin()) call their StrictMath counterparts, which are implemented in a platform-specific library, and the library’s method implementations are strict. Without strictfp, an intermediate calculation is not limited to the IEEE 754 32-bit and 64-bit floating-point representations that Java supports. Instead, the calculation can take advantage of a larger representation (perhaps 128 bits) on a platform that supports this representation. An intermediate calculation that overflows/underflows when its value is represented in 32/64 bits might not overflow/underflow when its value is represented in more bits. Because of this discrepancy, portability is compromised. strictfp levels the playing field by requiring all platforms to use 32/64 bits for intermediate calculations. When applied to a method, strictfp ensures that all floating-point calculations performed in that method are in strict compliance. However, strictfp can be used in a class header declaration (as in public strictfp class FourierTransform) to ensure that all floating-point calculations performed in that class are strict. ■ Note Math and StrictMath are declared final so that they cannot be extended. Also, they declare private empty noargument constructors so that they cannot be instantiated. Finally, Math and StrictMath are examples of utility classes because they exist as placeholders for static methods. Package The java.lang.Package class provides access to information about a package (see Chapter 3 for an introduction to packages). This information includes version details about the implementation and specification of a Java package, the name of the package, and an indication of whether or not the package has been sealed (all classes that are part of a package are archived in the same JAR file). Table 4-2 describes some of Package’s methods. 235 CHAPTER 4 TOURING LANGUAGE APIS Table 4-2. Package Methods 236 Method Description String getImplementationTitle() Return the title of this package’s implementation, which might be null. The format of the title is unspecified. String getImplementationVendor() Return the name of the vendor or organization that provides this package’s implementation. This name might be null. The format of the name is unspecified. String getImplementationVersion() Return the version number of this package’s implementation, which might be null. This version string must be a sequence of positive decimal integers separated by periods and might have leading zeros. String getName() Return the name of this package in standard dot notation; for example, java.lang. static Package getPackage(String packageName) Return the Package object that is associated with the package identified as packageName, or null when the package identified as packageName cannot be found. This method throws java.lang.NullPointerException when packageName is null. static Package[] getPackages() Return an array of all Package objects that are accessible to this method’s caller. String getSpecificationTitle() Return the title of this package’s specification, which might be null. The format of the title is unspecified. String getSpecificationVendor() Return the name of the vendor or organization that provides the specification that is implemented by this package. This name might be null. The format of the name is unspecified. String getSpecificationVersion() Return the version number of the specification of this package’s implementation, which might be null. This version string must be a sequence of positive decimal integers separated by periods, and might have leading zeros. boolean isCompatibleWith(String desired) Check this package to determine if it is compatible with the specified version string, by comparing this package’s specification version with the desired version. Return true when this package’s specification version number is greater than or equal to the desired version number (this package is compatible); otherwise, return false. This method throws NullPointerException when desired is null, and java.lang.NumberFormatException when this package’s version CHAPTER 4 TOURING LANGUAGE APIS number or the desired version number is not in dotted form. boolean isSealed() Return true when this package has been sealed; otherwise, return false. I have created a PackageInfo application that demonstrates most of Table 4-2’s Package methods. Listing 4-3 presents this application’s source code. Listing 4-3. Obtaining information about a package class PackageInfo { public static void main(String[] args) { if (args.length == 0) { System.err.println("usage: java PackageInfo packageName [version]"); return; } Package pkg = Package.getPackage(args[0]); if (pkg == null) { System.err.println(args[0]+" not found"); return; } System.out.println("Name: "+pkg.getName()); System.out.println("Implementation title: "+ pkg.getImplementationTitle()); System.out.println("Implementation vendor: "+ pkg.getImplementationVendor()); System.out.println("Implementation version: "+ pkg.getImplementationVersion()); System.out.println("Specification title: "+ pkg.getSpecificationTitle()); System.out.println("Specification vendor: "+ pkg.getSpecificationVendor()); System.out.println("Specification version: "+ pkg.getSpecificationVersion()); System.out.println("Sealed: "+pkg.isSealed()); if (args.length > 1) System.out.println("Compatible with "+args[1]+": "+ pkg.isCompatibleWith(args[1])); } } After compiling Listing 4-3 (javac PackageInfo.java), specify at least a package name on the command line when you run this application. For example, java PackageInfo java.lang returns the following output under Java 7: Name: java.lang Implementation title: Java Runtime Environment 237 CHAPTER 4 TOURING LANGUAGE APIS Implementation vendor: Oracle Corporation Implementation version: 1.7.0 Specification title: Java Platform API Specification Specification vendor: Oracle Corporation Specification version: 1.7 Sealed: false PackageInfo also lets you determine if the package’s specification is compatible with a specific version number. A package is compatible with its predecessors. For example, java PackageInfo java.lang 1.7 outputs Compatible with 1.7: true, whereas java PackageInfo java.lang 1.8 outputs Compatible with 1.8: false. You can also use PackageInfo with your own packages, which you learned to create in Chapter 3. For example, that chapter presented a logging package. Copy PackageInfo.class into the directory containing the logging package directory (which contains the compiled classfiles), and execute java PackageInfo logging. PackageInfo responds by displaying the following output: logging not found This error message is presented because getPackage() requires at least one classfile to be loaded from the package before it returns a Package object describing that package. The only way to eliminate the previous error message is to load a class from the package. Accomplish this task by merging the following code fragment into Listing 4-3. if (args.length == 3) try { Class.forName(args[2]); } catch (ClassNotFoundException cnfe) { System.err.println("cannot load "+args[2]); return; } This code fragment, which must precede Package pkg = Package.getPackage(args[0]);, loads the classfile named by the revised PackageInfo application’s third command-line argument. (I’ll discuss Class.forName() later in this chapter.) Run the new PackageInfo application via java PackageInfo logging 1.5 logging.File and you will observe the following output—this command line identifies logging’s File class as the class to load: Name: logging Implementation title: null Implementation vendor: null Implementation version: null Specification title: null Specification vendor: null Specification version: null Sealed: false Exception in thread "main" java.lang.NumberFormatException: Empty version string at java.lang.Package.isCompatibleWith(Package.java:228) at PackageInfo.main(PackageInfo.java:42) 238 CHAPTER 4 TOURING LANGUAGE APIS It is not surprising to see all of these null values because no package information has been added to the logging package. Also, NumberFormatException is thrown from isCompatibleWith() because the logging package does not contain a specification version number in dotted form (it is null). Perhaps the simplest way to place package information into the logging package is to create a logging.jar file in a similar manner to the example shown in Chapter 3. But first, you must create a small text file that contains the package information. You can choose any name for the file. Listing 4-4 reveals my choice of manifest.mf. Listing 4-4. manifest.mf containing the package information Implementation-Title: Logging Implementation Implementation-Vendor: Jeff Friesen Implementation-Version: 1.0a Specification-Title: Logging Specification Specification-Vendor: Jeff Friesen Specification-Version: 1.0 Sealed: true ■ Note Make sure to press the Return/Enter key at the end of the final line (Sealed: true). Otherwise, you will probably observe Sealed: false in the output because this entry will not be stored in the logging package by the JDK’s jar tool—jar is a bit quirky. Execute the following command line to create a JAR file that includes logging and its files, and whose manifest, a special file named MANIFEST.MF that stores information about the contents of a JAR file, contains the contents of Listing 4-4: jar cfm logging.jar manifest.mf logging/*.class This command line creates a JAR file named logging.jar (via the c [create] and f [file] options). It also merges the contents of manifest.mf (via the m [manifest] option) into MANIFEST.MF, which is stored in the package’s META-INF directory. ■ Note To learn more about a JAR file’s manifest, read the “JAR Manifest” section of the JDK documentation’s “JAR File Specification” page (http://download.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#JAR Manifest). Assuming that the jar tool presents no error messages, execute the following Windows-oriented command line (or a command line suitable for your platform) to run PackageInfo and extract the package information from the logging package: java -cp logging.jar;. PackageInfo logging 1.0 logging.File This time, you should see the following output: 239 CHAPTER 4 TOURING LANGUAGE APIS Name: logging Implementation title: Logging Implementation Implementation vendor: Jeff Friesen Implementation version: 1.0a Specification title: Logging Specification Specification vendor: Jeff Friesen Specification version: 1.0 Sealed: true Compatible with 1.0: true Primitive Type Wrapper Class The java.lang package includes Boolean, Byte, Character, Double, Float, Integer, Long, and Short. These classes are known as primitive type wrapper classes because their instances wrap themselves around values of primitive types. This book was purchased by [email protected] ■ Note The primitive type wrapper classes are also known as value classes. Java provides these eight primitive type wrapper classes for two reasons: • The Collections Framework (discussed in Chapter 5) provides lists, sets, and maps that can only store objects; they cannot store primitive values. You store a primitive value in a primitive type wrapper class instance and store the instance in the collection. • These classes provide a good place to associate useful constants (such as MAX_VALUE and MIN_VALUE) and class methods (such as Integer’s parseInt() methods and Character’s isDigit(), isLetter(), and toUpperCase() methods) with the primitive types. This section introduces you to each of these primitive type wrapper classes and a java.lang class named Number. Boolean Boolean is the smallest of the primitive type wrapper classes. This class declares three constants, including TRUE and FALSE, which denote precreated Boolean objects. It also declares a pair of constructors for initializing a Boolean object: • Boolean(boolean value) initializes the Boolean object to value. • Boolean(String s) converts s’s text to a true or false value and stores this value in the Boolean object. The second constructor compares s’s value with true. Because the comparison is case-insensitive, any uppercase/lowercase combination of these four letters (such as true, TRUE, or tRue) results in true being stored in the object. Otherwise, the constructor stores false in the object. 240 CHAPTER 4 TOURING LANGUAGE APIS ■ Note Boolean’s constructors are complemented by boolean booleanValue(), which returns the wrapped Boolean value. Boolean also declares or overrides the following methods: • int compareTo(Boolean b) compares the current Boolean object with b to determine their relative order. The method returns 0 when the current object contains the same Boolean value as b, a positive value when the current object contains true and b contains false, and a negative value when the current object contains false and b contains true. • boolean equals(Object o) compares the current Boolean object with o and returns true when o is not null, o is of type Boolean, and both objects contain the same Boolean value. • static boolean getBoolean(String name) returns true when a system property (discussed later in this chapter) identified by name exists and is equal to true. • int hashCode() returns a suitable hash code that allows Boolean objects to be used with hash-based collections (discussed in Chapter 5). • static boolean parseBoolean(String s) parses s, returning true if s equals "true", "TRUE", "True", or any other uppercase/lowercase combination. Otherwise, this method returns false. (Parsing breaks a sequence of characters into meaningful components, known as tokens.) • String toString() returns "true" when the current Boolean instance contains true; otherwise, this method returns "false". • static String toString(boolean b) returns "true" when b contains true; otherwise, this method returns "false". • static Boolean valueOf(boolean b) returns TRUE when b contains true or FALSE when b contains false. • static Boolean valueOf(String s) returns TRUE when s equals "true", "TRUE", "True", or any other uppercase/lowercase combination of these letters. Otherwise, this method returns FALSE. ■ Caution Newcomers to the Boolean class often think that getBoolean() returns a Boolean object’s true/false value. However, getBoolean() returns the value of a Boolean-based system property—I discuss system properties later in this chapter. If you need to return a Boolean object’s true/false value, use the booleanValue() method instead. 241 CHAPTER 4 TOURING LANGUAGE APIS It is often better to use TRUE and FALSE than to create Boolean objects. For example, suppose you need a method that returns a Boolean object containing true when the method’s double argument is negative, or false when this argument is zero or positive. You might declare your method like the following isNegative() method: Boolean isNegative(double d) { return new Boolean(d < 0); } Although this method is concise, it unnecessarily creates a Boolean object. When the method is called frequently, many Boolean objects are created that consume heap space. When heap space runs low, the garbage collector runs and slows down the application, which impacts performance. The following example reveals a better way to code isNegative(): Boolean isNegative(double d) { return (d < 0) ? Boolean.TRUE : Boolean.FALSE; } This method avoids creating Boolean objects by returning either the precreated TRUE or FALSE object. ■ Tip You should strive to create as few objects as possible. Not only will your applications have smaller memory footprints, they’ll perform better because the garbage collector will not run as often. Character Character is the largest of the primitive type wrapper classes, containing many constants, a constructor, many methods, and a trio of nested classes (Subset, UnicodeBlock, and UnicodeScript). ■ Note Character’s complexity derives from Java’s support for Unicode (http://en.wikipedia.org/wiki/Unicode). For brevity, I ignore much of Character’s Unicode-related complexity, which is beyond the scope of this chapter. Character declares a single Character(char value) constructor, which you use to initialize a Character object to value. This constructor is complemented by char charValue(), which returns the wrapped character value. When you start writing applications, you might codify expressions such as ch >= '0' && ch <= '9' (test ch to see if it contains a digit) and ch >= 'A' && ch <= 'Z' (test ch to see if it contains an uppercase letter). You should avoid doing so for three reasons: • 242 It is too easy to introduce a bug into the expression. For example, ch > '0' && ch <= '9' introduces a subtle bug that does not include '0' in the comparison. CHAPTER 4 TOURING LANGUAGE APIS • The expressions are not very descriptive of what they are testing. • The expressions are biased toward Latin digits (0-9) and letters (A-Z and a-z). They do not take into account digits and letters that are valid in other languages. For example, '\u0beb' is a character literal representing one of the digits in the Tamil language. Character declares several comparison and conversion class methods that address these concerns. These methods include the following: • static boolean isDigit(char ch) returns true when ch contains a digit (typically 0 through 9, but also digits in other alphabets). • static boolean isLetter(char ch) returns true when ch contains a letter (typically A-Z or a-z, but also letters in other alphabets). • static boolean isLetterOrDigit(char ch) returns true when ch contains a letter or digit (typically A-Z, a-z, or 0-9; but also letters or digits in other alphabets). • static boolean isLowerCase(char ch) returns true when ch contains a lowercase letter. • static boolean isUpperCase(char ch) returns true when ch contains an uppercase letter. • static boolean isWhitespace(char ch) returns true when ch contains a whitespace character (typically a space, a horizontal tab, a carriage return, or a line feed). • static char toLowerCase(char ch) returns the lowercase equivalent of ch’s uppercase letter; otherwise, this method returns ch’s value. • static char toUpperCase(char ch) returns the uppercase equivalent of ch’s lowercase letter; otherwise, this method returns ch’s value. For example, isDigit(ch) is preferable to ch >= '0' && ch <= '9' because it avoids a source of bugs, is more readable, and returns true for non-Latin digits (e.g., '\u0beb') as well as Latin digits. Float and Double Float and Double store floating-point and double precision floating-point values in Float and Double objects, respectively. These classes declare the following constants: • MAX_VALUE identifies the maximum value that can be represented as a float or double. • MIN_VALUE identifies the minimum value that can be represented as a float or double. • NaN represents 0.0F/0.0F as a float and 0.0/0.0 as a double. • NEGATIVE_INFINITY represents -infinity as a float or double. • POSITIVE_INFINITY represents +infinity as a float or double. 243 CHAPTER 4 TOURING LANGUAGE APIS Float and Double also declare the following constructors for initializing their objects: • Float(float value) initializes the Float object to value. • Float(double value) initializes the Float object to the float equivalent of value. • Float(String s) converts s’s text to a floating-point value and stores this value in the Float object. • Double(double value) initializes the Double object to value. • Double(String s) converts s’s text to a double precision floating-point value and stores this value in the Double object. Float’s constructors are complemented by float floatValue(), which returns the wrapped floating-point value. Similarly, Double’s constructors are complemented by double doubleValue(), which returns the wrapped double precision floating-point value. Float declares several utility methods as well as floatValue(). These methods include the following: • static int floatToIntBits(float value) converts value to a 32-bit integer. • static boolean isInfinite(float f) returns true when f’s value is +infinity or infinity. A related boolean isInfinite() method returns true when the current Float object’s value is +infinity or -infinity. • static boolean isNaN(float f) returns true when f’s value is NaN. A related boolean isNaN() method returns true when the current Float object’s value is NaN. • static float parseFloat(String s) parses s, returning the floating-point equivalent of s’s textual representation of a floating-point value, or throwing NumberFormatException when this representation is invalid (contains letters, for example). Double declares several utility methods as well as doubleValue(). These methods include the following: • static long doubleToLongBits(double value) converts value to a long integer. • static boolean isInfinite(double d) returns true when d’s value is +infinity or infinity. A related boolean isInfinite() method returns true when the current Double object’s value is +infinity or -infinity. • static boolean isNaN(double d) returns true when d’s value is NaN. A related boolean isNaN() method returns true when the current Double object’s value is NaN. • static double parseDouble(String s) parses s, returning the double precision floating-point equivalent of s’s textual representation of a double precision floating-point value, or throwing NumberFormatException when this representation is invalid. The floatToIntBits() and doubleToIntBits() methods are used in implementations of the equals() and hashCode() methods that must take float and double fields into account. floatToIntBits() and doubleToIntBits() allow equals() and hashCode() to respond properly to the following situations: 244 CHAPTER 4 TOURING LANGUAGE APIS • equals() must return true when f1 and f2 contain Float.NaN (or d1 and d2 contain Double.NaN). If equals() was implemented in a manner similar to f1.floatValue() == f2.floatValue() (or d1.doubleValue() == d2.doubleValue()), this method would return false because NaN is not equal to anything, including itself. • equals() must return false when f1 contains +0.0 and f2 contains -0.0 (or viceversa), or d1 contains +0.0 and d2 contains -0.0 (or vice-versa). If equals() was implemented in a manner similar to f1.floatValue() == f2.floatValue() (or d1.doubleValue() == d2.doubleValue()), this method would return true because +0.0 == -0.0 returns true. These requirements are needed for hash-based collections (discussed in Chapter 5) to work properly. Listing 4-5 shows how they impact Float’s and Double’s equals() methods: Listing 4-5. Demonstrating Float’s equals() method in a NaN context and Double’s equals() method in a +/-0.0 context class FloatDoubleDemo { public static void main(String[] args) { Float f1 = new Float(Float.NaN); System.out.println(f1.floatValue()); Float f2 = new Float(Float.NaN); System.out.println(f2.floatValue()); System.out.println(f1.equals(f2)); System.out.println(Float.NaN == Float.NaN); System.out.println(); Double d1 = new Double(+0.0); System.out.println(d1.doubleValue()); Double d2 = new Double(-0.0); System.out.println(d2.doubleValue()); System.out.println(d1.equals(d2)); System.out.println(+0.0 == -0.0); } } Compile Listing 4-5 (javac FloatDoubleDemo.java) and run this application (java FloatDoubleDemo). The following output proves that Float’s equals() method properly handles NaN and Double’s equals() method properly handles +/-0.0: NaN NaN true false 0.0 -0.0 false true 245 CHAPTER 4 TOURING LANGUAGE APIS ■ Tip If you want to test a float or double value for equality with +infinity or -infinity (but not both), do not use isInfinite(). Instead, compare the value with NEGATIVE_INFINITY or POSITIVE_INFINITY via ==. For example, f == Float.NEGATIVE_INFINITY. You will find parseFloat() and parseDouble() useful in many contexts. For example, Listing 4-6 uses parseDouble() to parse command-line arguments into doubles. Listing 4-6. Parsing command-line arguments into double precision floating-point values class Calc { public static void main(String[] args) { if (args.length != 3) { System.err.println("usage: java Calc value1 op value2"); System.err.println("op is one of +, -, *, or /"); return; } try { double value1 = Double.parseDouble(args[0]); double value2 = Double.parseDouble(args[2]); if (args[1].equals("+")) System.out.println(value1+value2); else if (args[1].equals("-")) System.out.println(value1-value2); else if (args[1].equals("*")) System.out.println(value1*value2); else if (args[1].equals("/")) System.out.println(value1/value2); else System.err.println("invalid operator: "+args[1]); } catch (NumberFormatException nfe) { System.err.println("Bad number format: "+nfe.getMessage()); } } } Specify java Calc 10E+3 + 66.0 to try out the Calc application. This application responds by outputting 10066.0. If you specified java Calc 10E+3 + A instead, you would observe Bad number format: For input string: "A" as the output, which is in response to the second parseDouble() method call’s throwing of a NumberFormatException object. 246 CHAPTER 4 TOURING LANGUAGE APIS Although NumberFormatException describes an unchecked exception, and although unchecked exceptions are often not handled because they represent coding mistakes, NumberFormatException does not fit this pattern in this example. The exception does not arise from a coding mistake; it arises from someone passing an illegal numeric argument to the application, which cannot be avoided through proper coding. Perhaps NumberFormatException should have been implemented as a checked exception type. Integer, Long, Short, and Byte Integer, Long, Short, and Byte store 32-bit, 64-bit, 16-bit, and 8-bit integer values in Integer, Long, Short, and Byte objects, respectively. Each class declares MAX_VALUE and MIN_VALUE constants that identify the maximum and minimum values that can be represented by its associated primitive type. These classes also declare the following constructors for initializing their objects: • Integer(int value) initializes the Integer object to value. • Integer(String s) converts s’s text to a 32-bit integer value and stores this value in the Integer object. • Long(long value) initializes the Long object to value. • Long(String s) converts s’s text to a 64-bit integer value and stores this value in the Long object. • Short(short value) initializes the Short object to value. • Short(String s) converts s’s text to a 16-bit integer value and stores this value in the Short object. • Byte(byte value) initializes the Byte object to value. • Byte(String s) converts s’s text to an 8-bit integer value and stores this value in the Byte object. Integer’s constructors are complemented by int intValue(), Long’s constructors are complemented by long longValue(), Short’s constructors are complemented by short shortValue(), and Byte’s constructors are complemented by byte byteValue(). These methods return wrapped integers. These classes declare various useful integer-oriented methods. For example, Integer declares the following utility methods for converting a 32-bit integer to a java.lang.String instance according to a specific representation (binary, hexadecimal, octal, and decimal): • static String toBinaryString(int i) returns a String object containing i’s binary representation. For example, Integer.toBinaryString(255) returns a String object containing 11111111. • static String toHexString(int i) returns a String object containing i’s hexadecimal representation. For example, Integer.toHexString(255) returns a String object containing ff. • static String toOctalString(int i) returns a String object containing i’s octal representation. For example, toOctalString(64) returns a String object containing 100. 247 CHAPTER 4 TOURING LANGUAGE APIS • static String toString(int i) returns a String object containing i’s decimal representation. For example, toString(255) returns a String object containing 255. It is often convenient to prepend zeros to a binary string so that you can align multiple binary strings in columns. For example, you might want to create an application that displays the following aligned output: 11110001 + 00000111 -------11111000 Unfortunately, toBinaryString() does not let you accomplish this task. For example, Integer.toBinaryString(7) returns a String object containing 111 instead of 00000111. Listing 4-7’s toAlignedBinaryString() method addresses this oversight. Listing 4-7. Aligning binary strings class AlignBinary { public static void main(String[] args) { System.out.println(toAlignedBinaryString(7, 8)); System.out.println(toAlignedBinaryString(255, 16)); System.out.println(toAlignedBinaryString(255, 7)); } static String toAlignedBinaryString(int i, int numBits) { String result = Integer.toBinaryString(i); if (result.length() > numBits) return null; // cannot fit result into numBits columns int numLeadingZeros = numBits-result.length(); String zerosPrefix = ""; for (int j = 0; j < numLeadingZeros; j++) zerosPrefix += "0"; return zerosPrefix+result; } } The toAlignedBinaryString() method takes two arguments: the first argument specifies the 32-bit integer that is to be converted into a binary string, and the second argument specifies the number of bit columns in which to fit the string. After calling toBinaryString() to return i’s equivalent binary string without leading zeros, toAlignedBinaryString() verifies that the string’s digits can fit into the number of bit columns specified by numBits. If they do not fit, this method returns null. (You will learn about length() and other String methods later in this chapter.) Moving on, toAlignedBinaryString() calculates the number of leading "0"s to prepend to result, and then uses a for loop to create a string of leading zeros. This method ends by returning the leading zeros string prepended to the result string. Although using the compound string concatenation with assignment operator (+=) in a loop to build a string looks okay, it is very inefficient because intermediate String objects are created and thrown 248 CHAPTER 4 TOURING LANGUAGE APIS away. However, I employed this inefficient code so that I can contrast it with the more efficient code that I present later in this chapter. When you run this application, it generates the following output: 00000111 0000000011111111 null Number Each of Float, Double, Integer, Long, Short, and Byte provides the other classes’ xValue() methods as well as its own xValue() method. For example, Float provides doubleValue(), intValue(), longValue(), shortValue(), and byteValue() as well as floatValue(). All six methods are members of Number, which is the abstract superclass of Float, Double, Integer, Long, Short, and Byte—Number’s floatValue(), doubleValue(), intValue(), and longValue() methods are abstract. Number is also the superclass of java.math.BigDecimal and java.math.BigInteger (discussed later in this chapter), and a pair of concurrency-related classes (one of these classes is presented in Chapter 6). Number exists to simplify iterating over a collection of Number subclass objects. For example, you can declare a variable of java.util.List<Number> type and initialize it to an instance of java.util.ArrayList<Number> (or ArrayList<>, for short). You can then store a mixture of Number subclass objects in the collection, and iterate over this collection by calling a subclass method polymorphically. Reference Chapter 2 introduced you to garbage collection, where you learned that the garbage collector removes an object from the heap when there are no more references to the object. This statement isn’t completely true, as you will shortly discover. Chapter 2 also introduced you to java.lang.Object’s finalize() method, where you learned that the garbage collector calls this method before removing an object from the heap. The finalize() method gives the object an opportunity to perform cleanup. This section continues from where Chapter 2 left off by introducing you to Java’s Reference API. After acquainting you with some basic terminology, it introduces you to the API’s Reference and ReferenceQueue classes, followed by the API’s SoftReference, WeakReference, and PhantomReference classes. These classes let applications interact with the garbage collector in limited ways. ■ Note As well as this section, you will find Brian Goetz’s “Java theory and practice: Plugging memory leaks with soft references” (http://www.ibm.com/developerworks/java/library/j-jtp01246/index.html) and “Java theory and practice: Plugging memory leaks with weak references” (http://www.ibm.com/developerworks/java/library/j-jtp11225/index.html) tutorials to be helpful in understanding the Reference API. 249 CHAPTER 4 TOURING LANGUAGE APIS Basic Terminology When an application runs, its execution reveals a root set of references, a collection of local variables, parameters, class fields, and instance fields that currently exist and that contain (possibly null) references to objects. This root set changes over time as the application runs. For example, parameters disappear after a method returns. Many garbage collectors identify this root set when they run. They use the root set to determine if an object is reachable (referenced, also known as live) or unreachable (not referenced). The garbage collector cannot collect reachable objects. Instead, it can only collect objects that, starting from the root set of references, cannot be reached. This book was purchased by [email protected] ■ Note Reachable objects include objects that are indirectly reachable from root-set variables, which means objects that are reachable through live objects that are directly reachable from those variables. An object that is unreachable by any path from any root-set variable is eligible for garbage collection. Beginning with Java 1.2, reachable objects are classified as strongly reachable, softly reachable, weakly reachable, and phantom reachable. Unlike strongly reachable objects, softly, weakly, and phantom reachable objects can be garbage collected. Going from strongest to weakest, the different levels of reachability reflect the life cycle of an object. They are defined as follows: 250 • An object is strongly reachable if it can be reached from some thread without traversing any Reference objects. A newly created object (such as the object referenced by d in Double d = new Double(1.0);) is strongly reachable by the thread that created it. (I will discuss threads later in this chapter.) • An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference (a reference to the object where the reference is stored in a SoftReference object). The strongest reference to this object is a soft reference. When the soft references to a softly reachable object are cleared, the object becomes eligible for finalization (discussed in Chapter 2). • An object is weakly reachable if it is neither strongly reachable nor softly reachable, but can be reached by traversing a weak reference (a reference to the object where the reference is stored in a WeakReference object). The strongest reference to this object is a weak reference. When the weak references to a weakly reachable object are cleared, the object becomes eligible for finalization. (Apart from the garbage collector being more eager to clean up the weakly reachable object, a weak reference is exactly like a soft reference.) • An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and it is referred to by some phantom reference (a reference to the object where the reference is stored in a PhantomReference object). The strongest reference to this object is a phantom reference. CHAPTER 4 TOURING LANGUAGE APIS • Finally, an object is unreachable, and therefore eligible for removal from memory during the next garbage collection cycle, when it is not reachable in any of the above ways. The object whose reference is stored in a SoftReference, WeakReference, or PhantomReference object is known as a referent. Reference and ReferenceQueue The Reference API consists of five classes located in the java.lang.ref package. Central to this package are Reference and ReferenceQueue. Reference is the abstract superclass of this package’s concrete SoftReference, WeakReference, and PhantomReference subclasses. ReferenceQueue is a concrete class whose instances describe queue data structures. When you associate a ReferenceQueue instance with a Reference subclass object (Reference object, for short), the Reference object is added to the queue when the referent to which its encapsulated reference refers becomes garbage. ■ Note You associate a ReferenceQueue object with a Reference object by passing the ReferenceQueue object to an appropriate Reference subclass constructor. Reference is declared as generic type Reference<T>, where T identifies the referent’s type. This class provides the following methods: • void clear() assigns null to the stored reference; the Reference object on which this method is called is not enqueued (inserted) into its associated reference queue (if there is an associated reference queue). (The garbage collector clears references directly; it does not call clear(). Instead, this method is called by applications.) • boolean enqueue() adds the Reference object on which this method is called to the associated reference queue. This method returns true when this Reference object has become enqueued; otherwise, this method returns false—this Reference object was already enqueued or was not associated with a queue when created. (The garbage collector enqueues Reference objects directly; it does not call enqueue(). Instead, this method is called by applications.) • T get() returns this Reference object’s stored reference. The return value is null when the stored reference has been cleared, either by the application or by the garbage collector. • boolean isEnqueued() returns true when this Reference object has been enqueued, either by the application or by the garbage collector. Otherwise, this method returns false—this Reference object was not associated with a queue when created. 251 CHAPTER 4 TOURING LANGUAGE APIS ■ Note Reference also declares constructors. Because these constructors are package-private, only classes in the java.lang.ref package can subclass Reference. This restriction is necessary because instances of Reference’s subclasses must work closely with the garbage collector. ReferenceQueue is declared as generic type ReferenceQueue<T>, where T identifies the referent’s type. This class declares the following constructor and methods: • ReferenceQueue() initializes a new ReferenceQueue instance. • Reference<? extends T> poll() polls this queue to check for an available Reference object. If one is available, the object is removed from the queue and returned. Otherwise, this method returns immediately with a null value. • Reference<? extends T> remove() removes the next Reference object from the queue and returns this object. This method waits indefinitely for a Reference object to become available, and throws java.lang.InterruptedException when this wait is interrupted. • Reference<? extends T> remove(long timeout) removes the next Reference object from the queue and returns this object. This method waits until a Reference object becomes available or until timeout milliseconds have elapsed—passing 0 to timeout causes the method to wait indefinitely. If timeout’s value expires, the method returns null. This method throws java.lang.IllegalArgumentException when timeout’s value is negative, or InterruptedException when this wait is interrupted. SoftReference The SoftReference class describes a Reference object whose referent is softly reachable. As well as inheriting Reference’s methods and overriding get(), this generic class provides the following constructors for initializing a SoftReference object: • SoftReference(T r) encapsulates r’s reference. The SoftReference object behaves as a soft reference to r. No ReferenceQueue object is associated with this SoftReference object. • SoftReference(T r, ReferenceQueue<? super T> q) encapsulates r’s reference. The SoftReference object behaves as a soft reference to r. The ReferenceQueue object identified by q is associated with this SoftReference object. Passing null to q indicates a soft reference without a queue. SoftReference is useful for implementing caches of objects that are expensive timewise to create (e.g., a database connection) and/or occupy significant amounts of heap space, such as large images. An image cache keeps images in memory (because it takes time to load them from disk) and ensures that duplicate (and possibly very large) images are not stored in memory. The image cache contains references to image objects that are already in memory. If these references were strong, the images would remain in memory. You would then need to figure out which images are no longer needed and remove them from memory so that they can be garbage collected. 252 CHAPTER 4 TOURING LANGUAGE APIS Having to manually remove images duplicates the work of a garbage collector. However, if you wrap the references to the image objects in SoftReference objects, the garbage collector will determine when to remove these objects (typically when heap memory runs low) and perform the removal on your behalf. Listing 4-8 shows how you could use SoftReference to cache an image. Listing 4-8. Caching an image import java.lang.ref.SoftReference; class Image { private byte[] image; private Image(String name) { image = new byte[1024*1024*100]; } static Image getImage(String name) { return new Image(name); } } class ImageCache { public static void main(String[] args) { Image image = Image.getImage("large.png"); System.out.println("caching image"); SoftReference<Image> cache = new SoftReference<>(image); image = null; byte[] b = new byte[1024]; while (cache.get() != null) { System.out.println("image is still cached"); b = new byte[b.length*10]; } System.out.println("image is no longer cached"); b = null; System.out.println("reloading and recaching image"); cache = new SoftReference<>(Image.getImage("large.png")); int counter = 0; while (cache.get() != null && ++counter != 7) System.out.println("image is still cached"); } } Listing 4-8 declares an Image class that simulates loading a large image, and an ImageCache class that demonstrates the SoftReference-based caching of an Image object. The main() method first creates an Image instance by calling the getImage() class method; the instance’s private image array occupies 100MB of memory. 253 CHAPTER 4 TOURING LANGUAGE APIS main() next creates a SoftReference object that is initialized to an Image object’s reference, and clears the strong reference to the Image object by assigning null to image. If this strong reference is not removed, the Image object will be cached always and the application will most likely run out of memory. After creating a byte array that’s used to demonstrate SoftReference, main() enters the application’s main loop, which keeps looping as long as cache.get() returns a nonnull reference (the Image object is still in the cache). For each loop iteration, main() outputs a message stating that the Image object is still cached, and doubles the size of the byte array. At some point, the array doubling will exhaust the heap space. However, before it throws an instance of the java.lang.OutOfMemoryError class, the Java Virtual Machine (JVM) will attempt to obtain sufficient memory by clearing the SoftReference object’s Image reference, and removing the Image object from the heap. The next loop iteration will detect this situation by discovering that get() returns null. The loop ends and main() outputs a suitable message confirming that the Image object is no longer cached. main() now assigns null to b to ensure that there will be sufficient memory to reload the large image (via getImage()) and once again store it in a SoftReference-based cache. Finally, main() enters a finite loop to demonstrate that the reloaded Image object is still in the cache. Compile Listing 4-8 (javac ImageCache.java) and run the application (java ImageCache). You should discover output that’s similar to that shown here: caching image image is still cached image is still cached image is still cached image is still cached image is still cached image is no longer cached reloading and recaching image image is still cached image is still cached image is still cached image is still cached image is still cached image is still cached WeakReference The WeakReference class describes a Reference object whose referent is weakly reachable. As well as inheriting Reference’s methods, this generic class provides the following constructors for initializing a WeakReference object: • WeakReference(T r) encapsulates r’s reference. The WeakReference object behaves as a weak reference to r. No ReferenceQueue object is associated with this WeakReference object. • WeakReference(T r, ReferenceQueue<? super T> q) encapsulates r’s reference. The WeakReference object behaves as a weak reference to r. The ReferenceQueue object identified by q is associated with this WeakReference object. Passing null to q indicates a weak reference without a queue. WeakReference is useful for preventing memory leaks related to hashmaps. A memory leak occurs when you keep adding objects to a hashmap and never remove them. The objects remain in memory because the hashmap stores strong references to them. 254 CHAPTER 4 TOURING LANGUAGE APIS Ideally, the objects should only remain in memory when they are strongly referenced from elsewhere in the application. When an object’s last strong reference (apart from hashmap strong references) disappears, the object should be garbage collected. This situation can be remedied by storing weak references to hashmap entries so they are discarded when no strong references to their keys exist. Java’s java.util.WeakHashMap class (discussed in Chapter 5), whose private Entry static member class extends WeakReference, accomplishes this task. ■ Note Reference queues are more useful with WeakReference than they are with SoftReference. In the context of WeakHashMap, these queues provide notification of weakly referenced keys that have been removed. Code within WeakHashMap uses the information provided by the queue to remove all hashmap entries that no longer have valid keys so that the value objects associated with these invalid keys can be garbage collected. However, a queue associated with SoftReference can alert the application that heap space is beginning to run low. PhantomReference The PhantomReference class describes a Reference object whose referent is phantom reachable. As well as inheriting Reference’s methods and overriding get(), this generic class provides a single constructor for initializing a PhantomReference object: • PhantomReference(T r, ReferenceQueue<? super T> q) encapsulates r’s reference. The PhantomReference object behaves as a phantom reference to r. The ReferenceQueue object identified by q is associated with this PhantomReference object. Passing null to q makes no sense because get() is overridden to return null and the PhantomReference object will never be enqueued. Although you cannot access a PhantomReference object’s referent (its get() method returns null), this class is useful because enqueuing the PhantomReference object signals that the referent has been finalized but its memory space has not yet been reclaimed. This signal lets you perform cleanup without using the finalize() method. The finalize() method is problematic because the garbage collector requires at least two garbage collection cycles to determine if an object that overrides finalize() can be garbage collected. When the first cycle detects that the object is eligible for garbage collection, it calls finalize(). Because this method might perform resurrection (see Chapter 2), which makes the unreachable object reachable, a second garbage collection cycle is needed to determine if resurrection has happened. This extra cycle slows down garbage collection. If finalize() is not overridden, the garbage collector does not need to call that method, and considers the object to be finalized. Hence, the garbage collector requires only one cycle. Although you cannot perform cleanup via finalize(), you can still perform cleanup via PhantomReference. Because there is no way to access the referent (get() returns null), resurrection cannot happen. Listing 4-9 shows how you might use PhantomReference to detect the finalization of a large object. Listing 4-9. Detecting a large object’s finalization import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; 255 CHAPTER 4 TOURING LANGUAGE APIS class LargeObject { private byte[] memory = new byte[1024*1024*50]; // 50 megabytes } class LargeObjectDemo { public static void main(String[] args) { ReferenceQueue<LargeObject> rq; rq = new ReferenceQueue<LargeObject>(); PhantomReference<LargeObject> pr; pr = new PhantomReference<LargeObject>(new LargeObject(), rq); byte[] b = new byte[1024]; while (rq.poll() == null) { System.out.println("waiting for large object to be finalized"); b = new byte[b.length*10]; } System.out.println("large object finalized"); System.out.println("pr.get() returns "+pr.get()); } } Listing 4-9 declares a LargeObject class whose private memory array occupies 50MB. If your JVM throws OutOfMemoryError when you run LargeObject, you might need to reduce the array’s size. The main() method first creates a ReferenceQueue object describing a queue onto which a PhantomReference object that initially contains a LargeObject reference will be enqueued. main() next creates the PhantomReference object, passing a reference to a newly created LargeObject object and a reference to the previously created ReferenceQueue object to the constructor. After creating a byte array that’s used to demonstrate PhantomReference, main() enters a polling loop. The polling loop begins by calling poll() to detect the finalization of the LargeObject object. As long as this method returns null, meaning that the LargeObject object is still unfinalized, the loop outputs a message and doubles the size of the byte array. At some point, heap space will exhaust and the garbage collector will attempt to obtain sufficient memory, by first clearing the PhantomReference object’s LargeObject reference and finalizing the LargeObject object prior to its removal from the heap. The PhantomReference object is then enqueued onto the rq-referenced ReferenceQueue; poll() returns the PhantomReference object. main() now exits the loop, outputs a message confirming the large object’s finalization, and outputs pr.get()’s return value, which is null proving that you cannot access a PhantomReference object’s referent. At this point, any additional cleanup operations related to the finalized object (such as closing a file that was opened in the file’s constructor but not otherwise closed) could be performed. Compile Listing 4-9 and run the application. You should see output that’s similar to that shown here: waiting for large object waiting for large object waiting for large object waiting for large object waiting for large object large object finalized 256 to to to to to be be be be be finalized finalized finalized finalized finalized CHAPTER 4 TOURING LANGUAGE APIS pr.get() returns null ■ Note For a more useful example of PhantomReference, check out Keith D Gregory’s “Java Reference Objects” blog post (http://www.kdgregory.com/index.php?page=java.refobj). Reflection Chapter 2 referred to reflection (also known as introspection) as a third form of runtime type identification (RTTI). Java’s Reflection API lets applications learn about loaded classes, interfaces, enums (a kind of class), and annotation types (a kind of interface). It also lets applications load classes dynamically, instantiate them, find a class’s fields and methods, access fields, call methods, and perform other tasks reflectively. Chapter 3 presented a StubFinder application that used part of the Reflection API to load a class and identify all the loaded class’s public methods that are annotated with @Stub annotations. This tool is one example where using reflection is beneficial. Another example is the class browser, a tool that enumerates the members of a class. ■ Caution Reflection should not be used indiscriminately. Application performance suffers because it takes longer to perform operations with reflection than without reflection. Also, reflection-oriented code can be harder to read, and the absence of compile-time type checking can result in runtime failures. The java.lang package’s Class class is the entry point into the Reflection API, whose types are mainly stored in the java.lang.reflect package. Class is generically declared as Class<T>, where T identifies the class, interface, enum, or annotation type that is being modeled by the Class object. T can be replaced by ? (as in Class<?>) when the type being modeled is unknown. Table 4-3 describes some of Class’s methods. Table 4-3. Class Methods Method Description static Class<?> forName(String typename) Return the Class object that is associated with typename, which must include the type’s qualified package name when the type is part of a package (java.lang.String, for example). If the class or interface type has not been loaded into memory, this method takes care of loading (reading the classfile’s contents into memory), linking (taking these contents and combining them into the runtime state of the JVM so that they can be executed), and initializing (setting class fields to default values, running class initializers, and performing other class initialization) prior to returning the Class object. This method 257 CHAPTER 4 TOURING LANGUAGE APIS throws java.lang.ClassNotFoundException when the type cannot be found, java.lang.LinkageError when an error occurs during linkage, and java.lang.ExceptionInInitializerError when an exception occurs during a class’s static initialization. 258 Annotation[] getAnnotations() Return an array (that’s possibly empty) containing all annotations that are declared for the class represented by this Class object. Class<?>[] getClasses() Return an array containing Class objects representing all public classes and interfaces that are members of the class represented by this Class object. This includes public class and interface members inherited from superclasses, and public class and interface members declared by the class. This method returns a zero-length array when this Class object has no public member classes or interfaces. This method also returns a zero-length array when this Class object represents a primitive type, an array class, or void. Constructor[] getConstructors() Return an array containing java.lang.reflect.Constructor objects representing all public constructors of the class represented by this Class object. A zero-length array is returned when the represented class has no public constructors, this Class object represents an array class, or this Class object represents a primitive type or void. Annotation[] getDeclaredAnnotations() Return an array containing all annotations that are directly declared on the class represented by this Class object— inherited annotations are not included. The returned array might be empty. Class<?>[] getDeclaredClasses() Return an array of Class objects representing all classes and interfaces declared as members of the class represented by this Class object. This includes public, protected, default (package) access, and private classes and interfaces declared by the class, but excludes inherited classes and interfaces. This method returns a zero-length array when the class declares no classes or interfaces as members, or when this Class object represents a primitive type, an array class, or void. Constructor[] getDeclaredConstructors() Return an array of Constructor objects representing all constructors declared by the class represented by this Class object. These are public, protected, default (package) access, and private constructors. The returned array’s elements are not sorted and are not in any order. If the class has a default constructor, it is included in the returned array. This method returns a zero-length array when this Class object represents an interface, a primitive type, an array class, or void. CHAPTER 4 TOURING LANGUAGE APIS Field[] getDeclaredFields() Return an array of java.lang.reflect.Field objects representing all fields declared by the class or interface represented by this Class object. This array includes public, protected, default (package) access, and private fields, but excludes inherited fields. The returned array’s elements are not sorted and are not in any order. This method returns a zerolength array when the class/interface declares no fields, or when this Class object represents a primitive type, an array class, or void. Method[] getDeclaredMethods() Return an array of java.lang.reflect.Method objects representing all methods declared by the class or interface represented by this Class object. This array includes public, protected, default (package) access, and private methods, but excludes inherited methods. The elements in the returned array are not sorted and are not in any order. This method returns a zero-length array when the class or interface declares no methods, or when this Class object represents a primitive type, an array class, or void. Field[] getFields() Return an array containing Field objects representing all public fields of the class or interface represented by this Class object, including those public fields inherited from superclasses and superinterfaces. The elements in the returned array are not sorted and are not in any order. This method returns a zero-length array when this Class object represents a class or interface with no accessible public fields, or when this Class object represents an array class, a primitive type, or void. Method[] getMethods() Return an array containing Method objects representing all public methods of the class or interface represented by this Class object, including those public methods inherited from superclasses and superinterfaces. Array classes return all the public member methods inherited from the Object class. The elements in the returned array are not sorted and are not in any order. This method returns a zero-length array when this Class object represents a class or interface that has no public methods, or when this Class object represents a primitive type or void. The class initialization method <clinit> (see Chapter 2) is not included in the returned array. int getModifiers() Returns the Java language modifiers for this class or interface, encoded in an integer. The modifiers consist of the JVM's constants for public, protected, private, final, static, abstract and interface; they should be decoded using the methods of class java.lang.reflect.Modifier. If the underlying class is an array class, then its public, private and protected modifiers are the same as those of its 259 CHAPTER 4 TOURING LANGUAGE APIS This book was purchased by [email protected] component type. If this Class object represents a primitive type or void, its public modifier is always true, and its protected and private modifiers are always false. If this Class object represents an array class, a primitive type or void, then its final modifier is always true and its interface modifier is always false. The values of its other modifiers are not determined by this specification. String getName() Return the name of the class represented by this Class object. Package getPackage() Return a Package object—I presented Package earlier in this chapter—that describes the package in which the class represented by this Class object is located, or null when the class is a member of the unnamed package. Class<? super T> getSuperclass() Return the Class object representing the superclass of the entity (class, interface, primitive type, or void) represented by this Class object. When the Class object on which this method is called represents the Object class, an interface, a primitive type, or void, null is returned. When this object represents an array class, the Class object representing the Object class is returned. boolean isAnnotation() Return true when this Class object represents an annotation type. If this method returns true, isInterface() also returns true because all annotation types are also interfaces. boolean isEnum() Return true if and only if this class was declared as an enum in the source code. boolean isInterface() Return true when this Class object represents an interface. T newInstance() Create and return a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized when it has not already been initialized. This method throws java.lang.IllegalAccessException when the class or its noargument constructor is not accessible; java.lang.InstantiationException when this Class object represents an abstract class, an interface, an array class, a primitive type, or void, or when the class does not have a noargument constructor (or when instantiation fails for some other reason); and ExceptionInInitializerError when initialization fails because the object threw an exception during initialization. Table 4-3’s description of the forName() method reveals one way to obtain a Class object. This method loads, links, and initializes a class or interface that is not in memory, and returns a Class object 260 CHAPTER 4 TOURING LANGUAGE APIS that represents the class or interface. Listing 4-10 demonstrates forName() and additional methods described in this table. Listing 4-10. Using reflection to decompile a type import import import import java.lang.reflect.Constructor; java.lang.reflect.Field; java.lang.reflect.Method; java.lang.reflect.Modifier; class Decompiler { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage: java Decompiler classname"); return; } try { decompileClass(Class.forName(args[0]), 0); } catch (ClassNotFoundException cnfe) { System.err.println("could not locate "+args[0]); } } static void decompileClass(Class<?> clazz, int indentLevel) { indent(indentLevel*3); System.out.print(Modifier.toString(clazz.getModifiers())+" "); if (clazz.isEnum()) System.out.println("enum "+clazz.getName()); else if (clazz.isInterface()) { if (clazz.isAnnotation()) System.out.print("@"); System.out.println(clazz.getName()); } else System.out.println(clazz); indent(indentLevel*3); System.out.println("{"); Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { indent(indentLevel*3); System.out.println(" "+fields[i]); } Constructor[] constructors = clazz.getDeclaredConstructors(); 261 CHAPTER 4 TOURING LANGUAGE APIS if (constructors.length != 0 && fields.length != 0) System.out.println(); for (int i = 0; i < constructors.length; i++) { indent(indentLevel*3); System.out.println(" "+constructors[i]); } Method[] methods = clazz.getDeclaredMethods(); if (methods.length != 0 && (fields.length != 0 || constructors.length != 0)) System.out.println(); for (int i = 0; i < methods.length; i++) { indent(indentLevel*3); System.out.println(" "+methods[i]); } Method[] methodsAll = clazz.getMethods(); if (methodsAll.length != 0 && (fields.length != 0 || constructors.length != 0 || methods.length != 0)) System.out.println(); if (methodsAll.length != 0) { indent(indentLevel*3); System.out.println(" ALL PUBLIC METHODS"); System.out.println(); } for (int i = 0; i < methodsAll.length; i++) { indent(indentLevel*3); System.out.println(" "+methodsAll[i]); } Class<?>[] members = clazz.getDeclaredClasses(); if (members.length != 0 && (fields.length != 0 || constructors.length != 0 || methods.length != 0 || methodsAll.length != 0)) System.out.println(); for (int i = 0; i < members.length; i++) if (clazz != members[i]) { decompileClass(members[i], indentLevel+1); if (i != members.length-1) System.out.println(); } indent(indentLevel*3); System.out.println("}"); } static void indent(int numSpaces) { for (int i = 0; i < numSpaces; i++) System.out.print(' '); } 262 CHAPTER 4 TOURING LANGUAGE APIS } Listing 4-10 presents the source code to a decompiler tool that uses reflection to obtain information about this tool’s solitary command-line argument, which must be a Java reference type (such as a class). The decompiler lets you output the type and name information for a class’s fields, constructors, methods, and nested types; it also lets you output the members of interfaces, enums, and annotation types. After verifying that one command-line argument has been passed to this application, main() calls forName() to try to return a Class object representing the class or interface identified by this argument. If successful, the returned object’s reference is passed to decompileClass(), which decompiles the type. forName() throws an instance of the checked ClassNotFoundException class when it cannot locate the class’s classfile (perhaps the classfile was erased prior to executing the application). It also throws LinkageError when a class’s classfile is malformed, and ExceptionInInitializerError when a class’s static initialization fails. ■ Note ExceptionInInitializerError is often thrown as the result of a class initializer throwing an unchecked exception. For example, the class initializer in the following FailedInitialization class results in ExceptionInInitializerError because someMethod() throws NullPointerException: class FailedInitialization { static { someMethod(null); } static void someMethod(String s) { int len = s.length(); // s contains null System.out.println(s+"'s length is "+len+" characters"); } public static void main(String[] args) { } } Much of the printing code is concerned with making the output look nice. For example, this code manages indentation, and only allows a newline character to be output to separate one section from another; a newline character is not output unless content appears before and after the newline. Listing 4-10 is recursive in that it invokes decompileClass() for every encountered nested type. 263 CHAPTER 4 TOURING LANGUAGE APIS Compile Listing 4-10 (javac Decompiler.java) and run this application with java.lang.Boolean as its solitary command line argument (java Decompiler java.lang.Boolean). You will observe the following output: public final class java.lang.Boolean { public static final java.lang.Boolean java.lang.Boolean.TRUE public static final java.lang.Boolean java.lang.Boolean.FALSE public static final java.lang.Class java.lang.Boolean.TYPE private final boolean java.lang.Boolean.value private static final long java.lang.Boolean.serialVersionUID public java.lang.Boolean(java.lang.String) public java.lang.Boolean(boolean) public int java.lang.Boolean.hashCode() public boolean java.lang.Boolean.equals(java.lang.Object) public java.lang.String java.lang.Boolean.toString() public static java.lang.String java.lang.Boolean.toString(boolean) public static int java.lang.Boolean.compare(boolean,boolean) public int java.lang.Boolean.compareTo(java.lang.Object) public int java.lang.Boolean.compareTo(java.lang.Boolean) public static java.lang.Boolean java.lang.Boolean.valueOf(boolean) public static java.lang.Boolean java.lang.Boolean.valueOf(java.lang.String) public boolean java.lang.Boolean.booleanValue() public static boolean java.lang.Boolean.getBoolean(java.lang.String) public static boolean java.lang.Boolean.parseBoolean(java.lang.String) private static boolean java.lang.Boolean.toBoolean(java.lang.String) ALL PUBLIC METHODS public int java.lang.Boolean.hashCode() public boolean java.lang.Boolean.equals(java.lang.Object) public java.lang.String java.lang.Boolean.toString() public static java.lang.String java.lang.Boolean.toString(boolean) public static int java.lang.Boolean.compare(boolean,boolean) public int java.lang.Boolean.compareTo(java.lang.Object) public int java.lang.Boolean.compareTo(java.lang.Boolean) public static java.lang.Boolean java.lang.Boolean.valueOf(boolean) public static java.lang.Boolean java.lang.Boolean.valueOf(java.lang.String) public boolean java.lang.Boolean.booleanValue() public static boolean java.lang.Boolean.getBoolean(java.lang.String) public static boolean java.lang.Boolean.parseBoolean(java.lang.String) public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException } 264 CHAPTER 4 TOURING LANGUAGE APIS The output reveals the difference between calling getDeclaredMethods() and getMethods(). For example, the output associated with getDeclaredMethods() includes the private toBoolean() method. Also, the output associated with getMethods() includes Object methods that are not overridden by Boolean; getClass() is an example. One of Table 4-3’s methods not demonstrated in Listing 4-10 is newInstance(), which is useful for instantiating a dynamically loaded class, provided that the class has a noargument constructor. Suppose you plan to create a viewer application that lets the user view different kinds of files. For example, the viewer can view the instruction sequence of a disassembled Windows EXE file, the graphical contents of a PNG file, or the contents of some other file. Furthermore, the user can choose to view this content in its normal state (disassembly versus graphical image, for example), in an informational manner (descriptive labels and content; for example, EXE HEADER: MZ), or as a table of hexadecimal values. The viewer application will start out with a few viewers, but you plan to add more viewers over time. You don’t want to integrate the viewer source code with the application source code because you would have to recompile the application and all of its viewers every time you added a new viewer (for example, a viewer that lets you view the contents of a Java classfile). Instead, you create these viewers in a separate project, and distribute their classfiles only. Also, you design the application to enumerate its currently accessible viewers when the application starts running (perhaps the viewers are stored in a JAR file), and present this list to the user. When the user selects a specific viewer from this list, the application loads the viewer’s classfile and instantiates this class via its Class object. The application can then invoke the object’s methods. Listing 4-11 presents the Viewer superclass that all viewer classes must extend. Listing 4-11. Abstracting a viewer abstract class Viewer { enum ViewMode { NORMAL, INFO, HEX }; abstract void view(byte[] content, ViewMode vm); } Viewer declares an enum to describe the three viewing modes. It also declares a view() method that displays the content of its byte array argument according to the viewer mode specified by its vm argument. Listing 4-12 presents a Viewer subclass for viewing an EXE file’s contents. Listing 4-12. A viewer for viewing EXE content class ViewerEXE extends Viewer { @Override void view(byte[] content, ViewMode vm) { switch (vm) { case NORMAL: System.out.println("outputting EXE content normally"); break; case INFO: System.out.println("outputting EXE content informationally"); break; 265 CHAPTER 4 TOURING LANGUAGE APIS case HEX: System.out.println("outputting EXE content in hexadecimal"); } } } ViewerEXE’s view() method demonstrates using the switch statement to switch on an enum constant. For brevity, I’ve limited this method to printing messages to standard output. Also, I don’t present the corresponding ViewPNG class, which has a similar structure. Listing 4-13 presents an application that dynamically loads ViewerEXE or ViewerPNG, instantiates the loaded class via newInstance(), and invokes the view() method. Listing 4-13. Loading, instantiating, and using Viewer subclasses class ViewerDemo { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage : java ViewerDemo filetype"); System.err.println("example: java ViewerDemo EXE"); return; } try { Class<?> clazz = Class.forName("Viewer"+args[0]); Viewer viewer = (Viewer) clazz.newInstance(); viewer.view(null, Viewer.ViewMode.HEX); } catch (ClassNotFoundException cnfe) { System.err.println("Class not found: "+cnfe.getMessage()); } catch (IllegalAccessException iae) { System.err.println("Illegal access: "+iae.getMessage()); } catch (InstantiationException ie) { System.err.println("Unable to instantiate loaded class"); } } } Assuming that you’ve compiled all source files (javac *.java, for example), execute java ViewerDemo EXE. You should observe the following output: outputting EXE content in hexadecimal If you were to execute java ViewerDemo PNG, you should see similar output. 266 CHAPTER 4 TOURING LANGUAGE APIS Suppose you attempted to load and instantiate the abstract Viewer class via java ViewerDemo "". Although this class would load, newInstance() would throw an instance of the InstantiationException class, and you would see the following output: Unable to instantiate loaded class Table 4-3’s descriptions of the getAnnotations() and getDeclaredAnnotations() methods reveal that each method returns an array of Annotation, an interface that is located in the java.lang.annotation package. Annotation is the superinterface of Override, SuppressWarnings, and all other annotation types. Table 4-3’s method descriptions also refer to Constructor, Field, and Method. Instances of these classes represent a class’s constructors and a class’s or an interface’s fields and methods. Constructor represents a constructor and is generically declared as Constructor<T>, where T identifies the class in which the constructor represented by Constructor is declared. Constructor declares various methods, including the following methods: • Annotation[] getDeclaredAnnotations() returns an array of all annotations declared on the constructor. The returned array has zero length when there are no annotations. • Class<T> getDeclaringClass() returns a Class object that represents the class in which the constructor is declared. • Class[]<?> getExceptionTypes() returns an array of Class objects representing the types of exceptions listed in the constructor’s throws clause. The returned array has zero length when there is no throws clause. • String getName() returns the constructor’s name. • Class[]<?> getParameterTypes() returns an array of Class objects representing the constructor’s parameters. The returned array has zero length when the constructor does not declare parameters. ■ Tip If you want to instantiate a class via a constructor that takes arguments, you cannot use Class’s newInstance() method. Instead, you must use Constructor’s T newInstance(Object... initargs) method to perform this task. Unlike Class’s newInstance() method, which bypasses the compile-time exception checking that would otherwise be performed by the compiler, Constructor’s newInstance() method avoids this problem by wrapping any exception thrown by the constructor in an instance of the java.lang.reflect.InvocationTargetException class. Field represents a field and declares various methods, including the following getter methods: • Object get(Object object) returns the value of the field for the specified object. • boolean getBoolean(Object object) returns the value of the Boolean field for the specified object. • byte getByte(Object object) returns the value of the byte integer field for the specified object. 267 CHAPTER 4 TOURING LANGUAGE APIS • char getChar(Object object) returns the value of the character field for the specified object. • double getDouble(Object object) returns the value of the double precision floating-point field for the specified object. • float getFloat(Object object) returns the value of the floating-point field for the specified object. • int getInt(Object object) returns the value of the integer field for the specified object. • long getLong(Object object) returns the value of the long integer field for the specified object. • short getShort(Object object) returns the value of the short integer field for the specified object. get() returns the value of any type of field. In contrast, the other listed methods return the values of specific types of fields. These methods throw NullPointerException when object is null and the field is an instance field, IllegalArgumentException when object is not an instance of the class or interface declaring the underlying field (or not an instance of a subclass or interface implementor), and IllegalAccessException when the underlying field cannot be accessed (it is private, for example). Listing 4-14 demonstrates Field’s getInt(Object) method along with its void setInt(Object obj, int i) counterpart. Listing 4-14. Reflectively getting and setting the values of instance and class fields import java.lang.reflect.Field; class X { public int i = 10; public static final double PI = 3.14; } class FieldAccessDemo { public static void main(String[] args) { try { Class<?> clazz = Class.forName("X"); X x = (X) clazz.newInstance(); Field f = clazz.getField("i"); System.out.println(f.getInt(x)); // Output: 10 f.setInt(x, 20); System.out.println(f.getInt(x)); // Output: 20 f = clazz.getField("PI"); System.out.println(f.getDouble(null)); // Output: 3.14 f.setDouble(x, 20); System.out.println(f.getDouble(null)); // Never executed } catch (Exception e) 268 CHAPTER 4 TOURING LANGUAGE APIS { System.err.println(e); } } } Listing 4-14 declares classes X and FieldAccessDemo. I’ve included X’s source code with FieldAccessDemo’s source code for convenience. However, you can imagine this source code being stored in a separate source file. FieldAccessDemo’s main() method first attempts to load X, and then tries to instantiate this class via newInstance(). If successful, the instance is assigned to reference variable x. main() next invokes Class’s Field getField(String name) method to return a Field instance that represents the public field identified by name, which happens to be i (in the first case) and PI (in the second case). This method throws java.lang.NoSuchFieldException when the named field doesn’t exist. Continuing, main() invokes Field’s getInt() and setInt() methods (with an object reference) to get the instance field’s initial value, change this value to another value, and get the new value. The initial and new values are output. At this point, main() demonstrates class field access in a similar manner. However, it passes null to getInt() and setInt() because an object reference isn’t required to access a class field. Because PI is declared final, the call to setInt() results in a thrown instance of the IllegalAccessException class. ■ Note I’ve specified catch (Exception e) to avoid having to specify multiple catch blocks. You could also use multicatch (see Chapter 3) where appropriate. Method represents a method and declares various methods, including the following methods: • int getModifiers() returns a 32-bit integer whose bit fields identify the method’s reserved word modifiers (such as public, abstract, or static). These bit fields must be interpreted via the Modifier class. For example, you might specify (method.getModifiers()&Modifier.ABSTRACT) == Modifier.ABSTRACT to find out if the method (represented by the Method object whose reference is stored in method) is abstract—this expression evaluates to true when the method is abstract. • Class<?> getReturnType() returns a Class object that represents the method’s return type. • Object invoke(Object receiver, Object... args) calls the method on the object identified by receiver (which is ignored when the method is a class method), passing the variable number of arguments identified by args to the called method. The invoke() method throws NullPointerException when receiver is null and the method being called is an instance method, IllegalAccessException when the method is not accessible (it is private, for example), IllegalArgumentException when an incorrect number of arguments are passed to the method (and other reasons), and InvocationTargetException when an exception is thrown from the called method. • boolean isVarArgs() returns true when the method is declared to receive a variable number of arguments. 269 CHAPTER 4 TOURING LANGUAGE APIS Listing 4-15 demonstrates Method’s invoke(Object, Object...) method. Listing 4-15. Reflectively invoking instance and class methods import java.lang.reflect.Method; This book was purchased by [email protected] class X { public void objectMethod(String arg) { System.out.println("Instance method: "+arg); } public static void classMethod() { System.out.println("Class method"); } } class MethodInvocationDemo { public static void main(String[] args) { try { Class<?> clazz = Class.forName("X"); X x = (X) clazz.newInstance(); Class[] argTypes = { String.class }; Method method = clazz.getMethod("objectMethod", argTypes); Object[] data = { "Hello" }; method.invoke(x, data); // Output: Instance method: Hello method = clazz.getMethod("classMethod", (Class<?>[]) null); method.invoke(null, (Object[]) null); // Output: Class method } catch (Exception e) { System.err.println(e); } } } Listing 4-15 declares classes X and MethodInvocationDemo. MethodInvocationDemo’s main() method first attempts to load X, and then tries to instantiate this class via newInstance(). If successful, the instance is assigned to reference variable x. main() next creates a one-element Class array that describes the types of objectMethod()’s parameter list. This array is used in the subsequent call to Class’s Method getMethod(String name, Class<?>... parameterTypes) method to return a Method object for invoking a public method named objectMethod with this parameter list. This method throws java.lang.NoSuchMethodException when the named method doesn’t exist. Continuing, main() creates an Object array that specifies the data to be passed to the method’s parameters; in this case, the array consists of a single String argument. It then reflectively invokes objectMethod() by passing this array along with the object reference stored in x to the invoke() method. At this point, main() shows you how to reflectively invoke a class method. The (Class<?>[]) and (Object[]) casts are used to suppress warning messages that have to do with variable numbers of 270 CHAPTER 4 TOURING LANGUAGE APIS arguments and null references. Notice that the first argument passed to invoke() is null when invoking a class method. The java.lang.reflect.AccessibleObject class is the superclass of Constructor, Field, and Method. This superclass provides methods for reporting a constructor’s, field’s, or method’s accessibility (is it private?) and making an inaccessible constructor, field, or method accessible. AccessibleObject’s methods include the following: • T getAnnotation(Class<T> annotationType) returns the constructor’s, field’s, or method’s annotation of the specified type when such an annotation is present; otherwise, null returns. • boolean isAccessible() returns true when the constructor, field, or method is accessible. • boolean isAnnotationPresent(Class<? extends Annotation> annotationType) returns true when an annotation of the type specified by annotationType has been declared on the constructor, field, or method. This method takes inherited annotations into account. • void setAccessible(boolean flag) attempts to make an inaccessible constructor, field, or method accessible when flag is true. ■ Note The java.lang.reflect package also includes an Array class whose class methods make it possible to reflectively create and access Java arrays. I previously showed you how to obtain a Class object via Class’s forName() method. Another way to obtain a Class object is to call Object’s getClass() method on an object reference; for example, Employee e = new Employee(); Class<? extends Employee> clazz = e.getClass();. The getClass() method does not throw an exception because the class from which the object was created is already present in memory. There is one more way to obtain a Class object, and that is to employ a class literal, which is an expression consisting of a class name, followed by a period separator, followed by reserved word class. Examples of class literals include Class<Employee> clazz = Employee.class; and Class<String> clazz = String.class. Perhaps you are wondering about how to choose between forName(), getClass(), and a class literal. To help you make your choice, the following list compares each competitor: • forName() is very flexible in that you can dynamically specify any reference type by its package-qualified name. If the type is not in memory, it is loaded, linked, and initialized. However, lack of compile-time type safety can lead to runtime failures. • getClass() returns a Class object describing the type of its referenced object. If called on a superclass variable containing a subclass instance, a Class object representing the subclass type is returned. Because the class is in memory, type safety is assured. 271 CHAPTER 4 TOURING LANGUAGE APIS • A class literal returns a Class object representing its specified class. Class literals are compact and the compiler enforces type safety by refusing to compile the source code when it cannot locate the literal’s specified class. ■ Note You can use class literals with primitive types, including void. Examples include int.class, double.class, and void.class. The returned Class object represents the class identified by a primitive type wrapper class’s TYPE field or java.lang.Void.TYPE. For example, each of int.class == Integer.TYPE and void.class == Void.TYPE evaluates to true. You can also use class literals with primitive type-based arrays. Examples include int[].class and double[].class. For these examples, the returned Class objects represent Class<int[]> and Class<double[]>. String String is the first predefined reference type presented in this book (in Chapter 1). Instances of this type represent sequences of characters, or strings. Unlike other reference types, the Java language treats the String class specially, by providing syntactic sugar that simplifies working with strings. For example, Java recognizes String favLanguage = "Java"; as the assignment of string literal "Java" to String variable favLanguage. Without this sugar, you would have to specify String favLanguage = new String("Java");. The Java language also overloads the + and += operators to perform string concatenation. Table 4-4 describes some of String’s constructors and methods for initializing String objects and working with strings. Table 4-4. String Constructors and Methods 272 Method Description String(char[] data) Initialize this String object to the data array’s characters. Modifying data after initializing this String object has no effect on the object. String(String s) Initialize this String object to s’s string. char charAt(int index) Return the character located at the zero-based index in this String object’s string. This method throws java.lang.StringIndexOutOfBoundsException when index is less than 0 or greater than or equal to the length of the string. String concat(String s) Return a new String object containing this String object’s string followed by the s argument’s string. CHAPTER 4 TOURING LANGUAGE APIS boolean endsWith(String suffix) Return true when this String object’s string ends with the characters in the suffix argument, when suffix is empty (contains no characters), or when suffix contains the same character sequence as this String object’s string. This method performs a case-sensitive comparison (a is not equal to A, for example), and throws NullPointerException when suffix is null. boolean equals(Object object) Return true when object is of type String and this argument’s string contains the same characters (and in the same order) as this String object’s string. boolean equalsIgnoreCase(String s) Return true when s and this String object contain the same characters (ignoring case). This method returns false when the character sequences differ or when null is passed to s. int indexOf(int c) Return the zero-based index of the first occurrence (from the start of the string to the end of the string) of the character represented by c in this String object’s string. Return -1 when this character is not present. int indexOf(String s) Return the zero-based index of the first occurrence (from the start of the string to the end of the string) of s’s character sequence in this String object’s string. Return -1 when s is not present. This method throws NullPointerException when s is null. String intern() Search an internal table of String objects for an object whose string is equal to this String object’s string. This String object’s string is added to the table when not present. Return the object contained in the table whose string is equal to this String object’s string. The same String object is always returned for strings that are equal. int lastIndexOf(int c) Return the zero-based index of the last occurrence (from the start of the string to the end of the string) of the character represented by c in this String object’s string. Return -1 when this character is not present. int lastIndexOf(String s) Return the zero-based index of the last occurrence (from the start of the string to the end of the string) of s’s character sequence in this String object’s string. Return -1 when s is not present. This method throws NullPointerException when s is null. int length() Return the number of characters in this String object’s string. String replace(char oldChar, Return a new String object whose string matches this String 273 CHAPTER 4 TOURING LANGUAGE APIS char newChar) object’s string except that all occurrences of oldChar have been replaced by newChar. String[] split(String expr) Split this String object’s string into an array of String objects using the regular expression (a string whose pattern [template] is used to search a string for substrings that match the pattern) specified by expr as the basis for the split. This method throws NullPointerException when expr is null and java.util.regex.PatternSyntaxException when expr’s syntax is invalid. boolean startsWith(String prefix) Return true when this String object’s string starts with the characters in the prefix argument, when prefix is empty (contains no characters), or when prefix contains the same character sequence as this String object’s string. This method performs a case-sensitive comparison (a is not equal to A, for example), and throws NullPointerException when prefix is null. String substring(int start) Return a new String object whose string contains this String object’s characters beginning with the character located at start. This method throws StringIndexOutOfBoundsException when start is negative or greater than the length of this String object’s string. char[] toCharArray() Return a character array that contains the characters in this String object’s string. String toLowerCase() Return a new String object whose string contains this String object’s characters where uppercase letters have been converted to lowercase. This String object is returned when it contains no uppercase letters to convert. String toUpperCase() Return a new String object whose string contains this String object’s characters where lowercase letters have been converted to uppercase. This String object is returned when it contains no lowercase letters to convert. String trim() Return a new String object that contains this String object’s string with whitespace characters (characters whose Unicode values are 32 or less) removed from the start and end of the string, or this String object if there is no leading/trailing whitespace. Table 4-4 reveals a couple of interesting items about String. First, this class’s String(String s) constructor does not initialize a String object to a string literal. Instead, it behaves similarly to the C++ copy constructor by initializing the String object to the contents of another String object. This behavior suggests that a string literal is more than what it appears to be. 274 CHAPTER 4 TOURING LANGUAGE APIS In reality, a string literal is a String object. You can prove this to yourself by executing System.out.println("abc".length()); and System.out.println("abc" instanceof String);. The first method call outputs 3, which is the length of the "abc" String object’s string, and the second method call outputs true ("abc" is a String object). ■ Note String literals are stored in a classfile data structure known as the constant pool. When a class is loaded, a String object is created for each literal and is stored in an internal table of String objects. The second interesting item is the intern() method, which interns (stores a unique copy of) a String object in an internal table of String objects. intern() makes it possible to compare strings via their references and == or !=. These operators are the fastest way to compare strings, which is especially valuable when sorting a huge number of strings. By default, String objects denoted by literal strings ("abc") and string-valued constant expressions ("a"+"bc") are interned in this table, which is why System.out.println("abc" == "a"+"bc"); outputs true. However, String objects created via String constructors are not interned, which is why System.out.println("abc" == new String("abc")); outputs false. In contrast, System.out.println("abc" == new String("abc").intern()); outputs true. ■ Caution Be careful with this string comparison technique (which only compares references) because you can easily introduce a bug when one of the strings being compared has not been interned. When in doubt, use the equals() or equalsIgnoreCase() method. Table 4-4 also reveals the charAt() and length() methods, which are useful for iterating over a string’s characters. For example, String s = "abc"; for (int i = 0; i < s.length(); i++) System.out.println(s.charAt(i)); returns each of s’s a, b, and c characters and outputs each character on a separate line. Finally, Table 4-4 presents split(), a method that I employed in Chapter 3’s StubFinder application to split a string’s comma-separated list of values into an array of String objects. This method uses a regular expression that identifies a sequence of characters around which the string is split. (I discuss regular expressions in Appendix C.) ■ Note StringIndexOutOfBoundsException and ArrayIndexOutOfBoundsException are sibling classes that share a common java.lang.IndexOutOfBoundsException superclass. 275 CHAPTER 4 TOURING LANGUAGE APIS StringBuffer and StringBuilder String objects are immutable: you cannot modify a String object’s string. The various String methods that appear to modify the String object actually return a new String object with modified string content instead. Because returning new String objects is often wasteful, Java provides the java.lang.StringBuffer and java.lang.StringBuilder classes as a workaround. These classes are identical apart from the fact that StringBuffer can be used in the context of multiple threads (discussed later in this chapter), and that StringBuilder is faster than StringBuffer but cannot be used in the context of multiple threads without explicit synchronization (also discussed later in this chapter). Table 4-5 describes some of StringBuffer’s constructors and methods for initializing StringBuffer objects and working with string buffers. StringBuilder’s constructors and methods are identical. Table 4-5. StringBuffer Constructors and Methods 276 Method Description StringBuffer() Initialize this StringBuffer object to an empty array with an initial capacity of 16 characters. StringBuffer(int capacity) Initialize this StringBuffer object to an empty array with an initial capacity of capacity characters. This constructor throws java.lang.NegativeArraySizeException when capacity is negative. StringBuffer(String s) Initialize this StringBuffer object to an array containing s’s characters. This object’s initial capacity is 16 plus the length of s. This constructor throws NullPointerException when s is null. StringBuffer append(boolean b) Append “true” to this StringBuffer object’s array when b is true and “false” to the array when b is false, and return this StringBuffer object. StringBuffer append(char ch) Append ch’s character to this StringBuffer object’s array, and return this StringBuffer object. StringBuffer append(char[] chars) Append the characters in the chars array to this StringBuffer object’s array, and return this StringBuffer object. This method throws NullPointerException when chars is null. StringBuffer append(double d) Append the string representation of d’s double precision floating-point value to this StringBuffer object’s array, and return this StringBuffer object. StringBuffer append(float f) Append the string representation of f’s floating-point value to this StringBuffer object’s array, and return this StringBuffer object. CHAPTER 4 TOURING LANGUAGE APIS StringBuffer append(int i) Append the string representation of i’s integer value to this StringBuffer object’s array, and return this StringBuffer object. StringBuffer append(long l) Append the string representation of l’s long integer value to this StringBuffer object’s array, and return this StringBuffer object. StringBuffer append(Object obj) Call obj’s toString() method and append the returned string’s characters to this StringBuffer object’s array. Append “null” to the array when null is passed to obj. Return this StringBuffer object. StringBuffer append(String s) Append s’s string to this StringBuffer object’s array. Append “null” to the array when null is passed to s. Return this StringBuffer object. int capacity() Return the current capacity of this StringBuffer object’s array. char charAt(int index) Return the character located at index in this StringBuffer object’s array. This method throws StringIndexOutOfBoundsException when index is negative or greater than or equal to this StringBuffer object’s length. void ensureCapacity(int min) Ensure that this StringBuffer object’s capacity is at least that specified by min. If the current capacity is less than min, a new internal array is created with greater capacity. The new capacity is set to the larger of min and the current capacity multiplied by 2, with 2 added to the result. No action is taken when min is negative or zero. int length() Return the number of characters stored in this StringBuffer object’s array. StringBuffer reverse() Return this StringBuffer object with its array contents reversed. void setCharAt(int index, char ch) Replace the character at index with ch. This method throws StringIndexOutOfBoundsException when index is negative or greater than or equal to the length of this StringBuffer object’s array. void setLength(int length) Set the length of this StringBuffer object’s array to length. If the length argument is less than the current length, the array’s contents are truncated. If the length argument is greater than or equal to the current length, sufficient null characters ('\u0000') are appended to the array. This method throws 277 CHAPTER 4 TOURING LANGUAGE APIS StringIndexOutOfBoundsException when length is negative. String substring(int start) Return a new String object that contains all characters in this StringBuffer object’s array starting with the character located at start. This method throws StringIndexOutOfBoundsException when start is less than 0 or greater than or equal to the length of this StringBuffer object’s array. String toString() Return a new String object whose string equals the contents of this StringBuffer object’s array. A StringBuffer or StringBuilder object’s internal array is associated with the concepts of capacity and length. Capacity refers to the maximum number of characters that can be stored in the array before the array grows to accommodate additional characters. Length refers to the number of characters that are already stored in the array. The toAlignedBinaryString() method presented earlier in this chapter included the following inefficient loop in its implementation: int numLeadingZeros = numBits-result.length(); String zerosPrefix = ""; for (int j = 0; j < numLeadingZeros; j++) zerosPrefix += "0"; This loop is inefficient because each of the iterations creates a StringBuilder object and a String object. The compiler transforms this code fragment into the following fragment: int numLeadingZeros = 3; String zerosPrefix = ""; for (int j = 0; j < numLeadingZeros; j++) zerosPrefix = new StringBuilder().append(zerosPrefix).append("0").toString(); A more efficient way to code the previous loop involves creating a StringBuffer/StringBuilder object prior to entering the loop, calling the appropriate append() method in the loop, and calling toString() after the loop. The following code fragment demonstrates this more efficient scenario: int numLeadingZeros = 3; StringBuilder sb = new StringBuilder(); for (int j = 0; j < numLeadingZeros; j++) sb.append("0"); String zerosPrefix = sb.toString(); ■ Caution Avoid using the string concatenation operator in a lengthy loop because it results in the creation of many unnecessary StringBuilder and String objects. 278 CHAPTER 4 TOURING LANGUAGE APIS System The java.lang.System class provides access to system-oriented resources, including standard input, standard output, and standard error. System declares in, out, and err class fields that support standard input, standard output, and standard error, respectively. The first field is of type java.io.InputStream, and the last two fields are of type java.io.PrintStream. (I will formally introduce these classes in Chapter 8.) System also declares various static methods, including those methods that are described in Table 46. Table 4-6. System Methods Method Description void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) Copy the number of elements specified by length from the src array starting at zero-based offset srcPos into the dest array starting at zero-based offset destPos. This method throws NullPointerException when src or dest is null, ArrayIndexOutOfBoundsException when copying causes access to data outside array bounds, and java.lang.ArrayStoreException when an element in the src array could not be stored into the dest array because of a type mismatch. long currentTimeMillis() Return the current system time in milliseconds since January 1, 1970 00:00:00 UTC. void gc() Inform the JVM that now would be a good time to run the garbage collector. This is only a hint; there is no guarantee that the garbage collector will run. String getProperty(String prop) Return the value of the system property (platform-specific attribute, such as a version number) identified by prop or null when no such property exists. void runFinalization() Inform the JVM that now would be a good time to perform any outstanding object finalizations. This is only a hint; there is no guarantee that outstanding object finalizations will be performed. void setErr(PrintStream err) Reassign the standard error stream to err. This is equivalent to specifying, for example, java Application 2>errlog on Windows XP. void setIn(InputStream in) Reassign the standard input stream to in. This is equivalent to specifying, for example, java Application <input on Windows XP. 279 CHAPTER 4 TOURING LANGUAGE APIS void setOut(PrintStream out) Reassign the standard output stream to out. This is equivalent to specifying, for example, java Application >output on Windows XP. Listing 4-16 demonstrates the arraycopy(), currentTimeMillis(), and getProperty() methods. Listing 4-16. Experimenting with System methods This book was purchased by [email protected] class SystemTasks { public static void main(String[] args) { int[] grades = { 86, 92, 78, 65, 52, 43, 72, 98, 81 }; int[] gradesBackup = new int[grades.length]; System.arraycopy(grades, 0, gradesBackup, 0, grades.length); for (int i = 0; i < gradesBackup.length; i++) System.out.println(gradesBackup[i]); System.out.println("Current time: "+System.currentTimeMillis()); String[] propNames = { "java.vendor.url", "java.class.path", "user.home", "java.class.version", "os.version", "java.vendor", "user.dir", "user.timezone", "path.separator", "os.name", "os.arch", "line.separator", "file.separator", "user.name", "java.version", "java.home" }; for (int i = 0; i < propNames.length; i++) System.out.println(propNames[i]+": "+ System.getProperty(propNames[i])); } } Listing 4-16’s main() method begins by demonstrating arraycopy(). It uses this method to copy the contents of a grades array to a gradesBackup array. 280 CHAPTER 4 TOURING LANGUAGE APIS ■ Tip The arraycopy() method is the fastest portable way to copy one array to another. Also, when you write a class whose methods return a reference to an internal array, you should use arraycopy() to create a copy of the array, and then return the copy’s reference. That way, you prevent clients from directly manipulating (and possibly screwing up) the internal array. main() next calls currentTimeMillis() to return the current time as a milliseconds value. Because this value is not human-readable, you might want to use the java.util.Date class (discussed in Appendix C). The Date() constructor calls currentTimeMillis() and its toString() method converts this value to a readable date and time. main() concludes by demonstrating getProperty() in a for loop. This loop iterates over all of Table 4-6’s property names, outputting each name and value. When I run this application on my platform, it generates the following output: 86 92 78 65 52 43 72 98 81 Current time: 1312236551718 java.vendor.url: http://java.oracle.com/ java.class.path: . user.home: C:\Documents and Settings\Jeff Friesen java.class.version: 51.0 os.version: 5.1 java.vendor: Oracle Corporation user.dir: C:\prj\dev\bj7\ch04\code\SystemTasks user.timezone: path.separator: ; os.name: Windows XP os.arch: x86 line.separator: file.separator: \ user.name: Jeff Friesen java.version: 1.7.0 java.home: C:\Program Files\Java\jdk1.7.0\jre ■ Note line.separator stores the actual line separator character/characters, not its/their representation (such as \r\n), which is why a blank line appears after line.separator:. 281 CHAPTER 4 TOURING LANGUAGE APIS Threading Applications execute via threads, which are independent paths of execution through an application’s code. When multiple threads are executing, each thread’s path can differ from other thread paths. For example, a thread might execute one of a switch statement’s cases, and another thread might execute another of this statement’s cases. ■ Note Applications use threads to improve performance. Some applications can get by with only the default main thread to carry out their tasks, but other applications need additional threads to perform time-intensive tasks in the background, so that they remain responsive to their users. The JVM gives each thread its own method-call stack to prevent threads from interfering with each other. Separate stacks let threads keep track of their next instructions to execute, which can differ from thread to thread. The stack also provides a thread with its own copy of method parameters, local variables, and return value. Java supports threads via its Threading API. This API consists of one interface (Runnable) and four classes (Thread, ThreadGroup, ThreadLocal, and InheritableThreadLocal) in the java.lang package. After exploring Runnable and Thread (and mentioning ThreadGroup during this exploration), this section explores thread synchronization, ThreadLocal, and InheritableThreadLocal. ■ Note Java 5 introduced the java.util.concurrent package as a high-level alternative to the low-level Threading API. (I will discuss this package in Chapter 6.) Although java.util.concurrent is the preferred API for working with threads, you should also be somewhat familiar with Threading because it is helpful in simple threading scenarios. Also, you might have to analyze someone else’s source code that depends on Threading. Runnable and Thread Java provides the Runnable interface to identify those objects that supply code for threads to execute via this interface’s solitary void run() method—a thread receives no arguments and returns no value. Classes implement Runnable to supply this code, and one of these classes is Thread. Thread provides a consistent interface to the underlying operating system’s threading architecture. (The operating system is typically responsible for creating and managing threads.) Thread makes it possible to associate code with threads, as well as start and manage those threads. Each Thread instance associates with a single thread. Thread declares several constructors for initializing Thread objects. Some of these constructors take Runnable arguments: you can supply code to run without having to extend Thread. Other constructors do not take Runnable arguments: you must extend Thread and override its run() method to supply the code to run. For example, Thread(Runnable runnable) initializes a new Thread object to the specified runnable whose code is to be executed. In contrast, Thread() does not initialize Thread to a Runnable argument. 282 CHAPTER 4 TOURING LANGUAGE APIS Instead, your Thread subclass provides a constructor that calls Thread(), and the subclass also overrides Thread’s run() method. In the absence of an explicit name argument, each constructor assigns a unique default name (starting with Thread-) to the Thread object. Names make it possible to differentiate threads. In contrast to the previous two constructors, which choose default names, Thread(String threadName) lets you specify your own thread name. Thread also declares methods for starting and managing threads. Table 4-7 describes many of the more useful methods. Table 4-7. Thread Methods Method Description static Thread currentThread() Return the Thread object associated with the thread that calls this method. String getName() Return the name associated with this Thread object. Thread.State getState() Return the state of the thread associated with this Thread object. The state is identified by the Thread.State enum as one of BLOCKED (waiting to acquire a lock, discussed later), NEW (created but not started), RUNNABLE (executing), TERMINATED (the thread has died), TIMED_WAITING (waiting for a specified amount of time to elapse), or WAITING (waiting indefinitely). void interrupt() Set the interrupt status flag in this Thread object. If the associated thread is blocked or waiting, clear this flag and wake up the thread by throwing an instance of the InterruptedException class. static boolean interrupted() Return true when the thread associated with this Thread object has a pending interrupt request. Clear the interrupt status flag. boolean isAlive() Return true to indicate that this Thread object’s associated thread is alive and not dead. A thread’s lifespan ranges from just before it is actually started within the start() method to just after it leaves the run() method, at which point it dies. boolean isDaemon() Return true when the thread associated with this Thread object is a daemon thread, a thread that acts as a helper to a user thread (nondaemon thread) and dies automatically when the application’s last nondaemon thread dies so the application can exit. boolean isInterrupted() Return true when the thread associated with this Thread object has a pending interrupt request. void join() The thread that calls this method on this Thread object waits for the thread associated with this object to die. This method 283 CHAPTER 4 TOURING LANGUAGE APIS throws InterruptedException when this Thread object’s interrupt() method is called. void join(long millis) The thread that calls this method on this Thread object waits for the thread associated with this object to die, or until millis milliseconds have elapsed, whichever happens first. This method throws InterruptedException when this Thread object’s interrupt() method is called. void setDaemon(boolean isDaemon) Mark this Thread object’s associated thread as a daemon thread when isDaemon is true. This method throws java.lang.IllegalThreadStateException when the thread has not yet been created and started. void setName(String threadName) Assign threadName’s value to this Thread object as the name of its associated thread. static void sleep(long time) Pause the thread associated with this Thread object for time milliseconds. This method throws InterruptedException when this Thread object’s interrupt() method is called while the thread is sleeping. void start() Create and start this Thread object’s associated thread. This method throws IllegalThreadStateException when the thread was previously started and is running or has died. Listing 4-17 introduces you to the Threading API via a main() method that demonstrates Runnable, Thread(Runnable runnable), currentThread(), getName(), and start(). Listing 4-17. A pair of counting threads class CountingThreads { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { String name = Thread.currentThread().getName(); int count = 0; while (true) System.out.println(name+": "+count++); } }; Thread thdA = new Thread(r); Thread thdB = new Thread(r); thdA.start(); 284 CHAPTER 4 TOURING LANGUAGE APIS thdB.start(); } } According to Listing 4-17, the default main thread that executes main() first instantiates an anonymous class that implements Runnable. It then creates two Thread objects, initializing each object to the runnable, and calls Thread’s start() method to create and start both threads. After completing these tasks, the main thread exits main() and dies. Each of the two started threads executes the runnable’s run() method. It calls Thread’s currentThread() method to obtain its associated Thread instance, uses this instance to call Thread’s getName() method to return its name, initializes count to 0, and enters an infinite loop where it outputs name and count, and increments count on each iteration. ■ Tip To stop an application that does not end, press the Ctrl and C keys simultaneously (at least on Windows platforms). I observe both threads alternating in their execution when I run this application on the Windows XP platform. Partial output from one run appears here: Thread-0: Thread-0: Thread-0: Thread-0: Thread-0: Thread-0: Thread-0: Thread-0: Thread-1: Thread-1: Thread-1: Thread-1: 0 1 2 3 4 5 6 7 0 1 2 3 The operating system assigns a separate thread to each processor or core so the threads execute concurrently (at the same time). When a computer does not have enough processors and/or cores, a thread must wait its turn to use the shared processor/core. The operating system uses a scheduler (http://en.wikipedia.org/wiki/Scheduling_(computing)) to determine when a waiting thread executes. The following list identifies three different schedulers: • Linux 2.6 through 2.6.22 uses the O(1) scheduler (http://en.wikipedia.org/wiki/O(1)_scheduler) . • Linux 2.6.23 uses the Completely Fair Scheduler (http://en.wikipedia.org/wiki/Completely_Fair_Scheduler). • Windows NT-based operating systems (e.g., NT, 2000, XP, Vista, and 7) use a multilevel feedback queue scheduler (http://en.wikipedia.org/wiki/Multilevel_feedback_queue). 285 o CHAPTER 4 TOURING LANGUAGE APIS The previous output from the counting threads application resulted from running this application via Windows XP’s multilevel feedback queue scheduler. Because of this scheduler, both threads take turns executing. ■ Caution Although this output indicates that the first thread starts executing, never assume that the thread associated with the Thread object whose start() method is called first is the first thread to execute. While this might be true of some schedulers, it might not be true of others. A multilevel feedback queue and many other thread schedulers take the concept of priority (thread relative importance) into account. They often combine preemptive scheduling (higher priority threads preempt—interrupt and run instead of—lower priority threads) with round robin scheduling (equal priority threads are given equal slices of time, which are known as time slices, and take turns executing). Thread supports priority via its void setPriority(int priority) method (set the priority of this Thread object’s thread to priority, which ranges from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY— Thread.NORMAL_PRIORITY identifies the default priority) and int getPriority() method (return the current priority). ■ Caution Using the setPriority() method can impact an application’s portability across platforms because different schedulers can handle a priority change in different ways. For example, one platform’s scheduler might delay lower priority threads from executing until higher priority threads finish. This delaying can lead to indefinite postponement or starvation because lower priority threads “starve” while waiting indefinitely for their turn to execute, and this can seriously hurt the application’s performance. Another platform’s scheduler might not indefinitely delay lower priority threads, improving application performance. Listing 4-18 refactors Listing 4-17’s main() method to give each thread a nondefault name, and to put each thread to sleep after outputting name and count. Listing 4-18. A pair of counting threads revisited class CountingThreads { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { String name = Thread.currentThread().getName(); int count = 0; 286 CHAPTER 4 TOURING LANGUAGE APIS while (true) { System.out.println(name+": "+count++); try { Thread.sleep(100); } catch (InterruptedException ie) { } } } }; Thread thdA = new Thread(r); thdA.setName("A"); Thread thdB = new Thread(r); thdB.setName("B"); thdA.start(); thdB.start(); } } Listing 4-18 reveals that Threads A and B execute Thread.sleep(100); to sleep for 100 milliseconds. This sleep results in each thread executing more frequently, as the following partial output reveals: A: B: A: B: A: B: A: B: 0 0 1 1 2 2 3 3 A thread will occasionally start another thread to perform a lengthy calculation, download a large file, or perform some other time-consuming activity. After finishing its other tasks, the thread that started the worker thread is ready to process the results of the worker thread and waits for the worker thread to finish and die. It is possible to wait for the worker thread to die by using a while loop that repeatedly calls Thread’s isAlive() method on the worker thread’s Thread object and sleeps for a certain length of time when this method returns true. However, Listing 4-19 demonstrates a less verbose alternative: the join() method. Listing 4-19. Joining the default main thread with a background thread class JoinDemo { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { 287 CHAPTER 4 TOURING LANGUAGE APIS System.out.println("Worker thread is simulating "+ "work by sleeping for 5 seconds."); try { Thread.sleep(5000); } catch (InterruptedException ie) { } System.out.println("Worker thread is dying"); } }; Thread thd = new Thread(r); thd.start(); System.out.println("Default main thread is doing work."); try { Thread.sleep(2000); } catch (InterruptedException ie) { } System.out.println("Default main thread has finished its work."); System.out.println("Default main thread is waiting for worker thread "+ "to die."); try { thd.join(); } catch (InterruptedException ie) { } System.out.println("Main thread is dying"); } } Listing 4-19 demonstrates the default main thread starting a worker thread, performing some work, and then waiting for the worker thread to die by calling join() via the worker thread’s thd object. When you run this application, you will discover output similar to the following (message order might differ somewhat): Default main thread is doing work. Worker thread is simulating work by sleeping for 5 seconds. Default main thread has finished its work. Default main thread is waiting for worker thread to die. Worker thread is dying Main thread is dying Every Thread object belongs to some ThreadGroup object; Thread declares a ThreadGroup getThreadGroup() method that returns this object. You should ignore thread groups because they are not that useful. If you need to logically group Thread objects, you should use an array or collection instead. 288 CHAPTER 4 TOURING LANGUAGE APIS ■ Caution Various ThreadGroup methods are flawed. For example, int enumerate(Thread[] threads) will not include all active threads in its enumeration when its threads array argument is too small to store their Thread objects. Although you might think that you could use the return value from the int activeCount() method to properly size this array, there is no guarantee that the array will be large enough because activeCount()’s return value fluctuates with the creation and death of threads. However, you should still know about ThreadGroup because of its contribution in handling exceptions that are thrown while a thread is executing. Listing 4-20 sets the stage for learning about exception handling by presenting a run() method that attempts to divide an integer by 0, which results in a thrown java.lang.ArithmeticException instance. Listing 4-20. Throwing an exception from the run() method class ExceptionThread { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { int x = 1/0; } }; Thread thd = new Thread(r); thd.start(); } } Run this application and you will see an exception trace that identifies the thrown ArithmeticException: Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero at ExceptionThread$1.run(ExceptionThread.java:10) at java.lang.Thread.run(Thread.java:722) When an exception is thrown out of the run() method, the thread terminates and the following activities take place: • The JVM looks for an instance of Thread.UncaughtExceptionHandler installed via Thread’s void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) method. When this handler is found, it passes execution to the instance’s void uncaughtException(Thread t, Throwable e) method, where t identifies the Thread object of the thread that threw the exception, and e identifies the thrown exception or error—perhaps an OutOfMemoryError instance was thrown. If this method throws an exception/error, the exception/error is ignored by the JVM. 289 CHAPTER 4 TOURING LANGUAGE APIS • Assuming that setUncaughtExceptionHandler() was not called to install a handler, the JVM passes control to the associated ThreadGroup object’s uncaughtException(Thread t, Throwable e) method. Assuming that ThreadGroup was not extended, and that its uncaughtException() method was not overridden to handle the exception, uncaughtException() passes control to the parent ThreadGroup object’s uncaughtException() method when a parent ThreadGroup is present. Otherwise, it checks to see if a default uncaught exception handler has been installed (via Thread’s static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) method.) If a default uncaught exception handler has been installed, its uncaughtException() method is called with the same two arguments. Otherwise, uncaughtException() checks its Throwable argument to determine if it is an instance of java.lang.ThreadDeath. If so, nothing special is done. Otherwise, as Listing 4-20’s exception message shows, a message containing the thread's name, as returned from the thread's getName() method, and a stack backtrace, using the Throwable argument’s printStackTrace() method, is printed to the standard error stream. This book was purchased by [email protected] Listing 4-21 demonstrates Thread’s setUncaughtExceptionHandler() and setDefaultUncaughtExceptionHandler() methods. Listing 4-21. Demonstrating uncaught exception handlers class ExceptionThread { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { int x = 1/0; } }; Thread thd = new Thread(r); Thread.UncaughtExceptionHandler uceh; uceh = new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, { System.out.println("Caught throwable } }; thd.setUncaughtExceptionHandler(uceh); uceh = new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, { System.out.println("Default uncaught System.out.println("Caught throwable } 290 Throwable e) "+e+" for thread "+t); Throwable e) exception handler"); "+e+" for thread "+t); CHAPTER 4 TOURING LANGUAGE APIS }; thd.setDefaultUncaughtExceptionHandler(uceh); thd.start(); } } When you run this application, you will observe the following output: Caught throwable java.lang.ArithmeticException: / by zero for thread Thread[Thread-0,5,main] You also will not see the default uncaught exception handler’s output because the default handler is not called. To see that output, you must comment out thd.setUncaughtExceptionHandler(uceh);. If you also comment out thd.setDefaultUncaughtExceptionHandler(uceh);, you will see Listing 4-20’s output. ■ Caution Thread declares several deprecated methods, including stop() (stop an executing thread). These methods have been deprecated because they are unsafe. Do not use these deprecated methods. (I will show you how to safely stop a thread later in this chapter.) Also, you should avoid the static void yield() method, which is intended to switch execution from the current thread to another thread, because it can affect portability and hurt application performance. Although yield() might switch to another thread on some platforms (which can improve performance), yield() might only return to the current thread on other platforms (which hurts performance because the yield() call has only wasted time). Thread Synchronization Throughout its execution, each thread is isolated from other threads because it has been given its own method-call stack. However, threads can still interfere with each other when they access and manipulate shared data. This interference can corrupt the shared data, and this corruption can cause an application to fail. For example, consider a checking account in which a husband and wife have joint access. Suppose that the husband and wife decide to empty this account at the same time without knowing that the other is doing the same thing. Listing 4-22 demonstrates this scenario. Listing 4-22. A problematic checking account class CheckingAccount { private int balance; CheckingAccount(int initialBalance) { balance = initialBalance; } boolean withdraw(int amount) { if (amount <= balance) { try 291 CHAPTER 4 TOURING LANGUAGE APIS { Thread.sleep((int)(Math.random()*200)); } catch (InterruptedException ie) { } balance -= amount; return true; } return false; } public static void main(String[] args) { final CheckingAccount ca = new CheckingAccount(100); Runnable r = new Runnable() { public void run() { String name = Thread.currentThread().getName(); for (int i = 0; i < 10; i++) System.out.println (name+" withdraws $10: "+ ca.withdraw(10)); } }; Thread thdHusband = new Thread(r); thdHusband.setName("Husband"); Thread thdWife = new Thread(r); thdWife.setName("Wife"); thdHusband.start(); thdWife.start(); } } This application lets more money be withdrawn than is available in the account. For example, the following output reveals $110 being withdrawn when only $100 is available: Wife withdraws $10: true Wife withdraws $10: true Husband withdraws $10: true Wife withdraws $10: true Husband withdraws $10: true Wife withdraws $10: true Husband withdraws $10: true Husband withdraws $10: true Husband withdraws $10: true Husband withdraws $10: true Husband withdraws $10: false Husband withdraws $10: false Husband withdraws $10: false Husband withdraws $10: false Wife withdraws $10: true Wife withdraws $10: false Wife withdraws $10: false 292 CHAPTER 4 TOURING LANGUAGE APIS Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false The reason why more money is withdrawn than is available for withdrawal is that a race condition exists between the husband and wife threads. ■ Note A race condition is a scenario in which multiple threads update the same object at the same time or nearly at the same time. Part of the object stores values written to it by one thread, and another part of the object stores values written to it by another thread. The race condition exists because the actions of checking the amount for withdrawal to ensure that it is less than what appears in the balance and deducting the amount from the balance are not atomic (indivisible) operations. (Although atoms are divisible, atomic is commonly used to refer to something being indivisible.) ■ Note The Thread.sleep() method call that sleeps for a variable amount of time (up to a maximum of 199 milliseconds) is present so that you can observe more money being withdrawn than is available for withdrawal. Without this method call, you might have to execute the application hundreds of times (or more) to witness this problem, because the scheduler might rarely pause a thread between the amount <= balance expression and the balance -= amount; expression statement—the code executes rapidly. Consider the following scenario: • The Husband thread executes withdraw()’s amount <= balance expression, which returns true. The scheduler pauses the Husband thread and lets the Wife thread execute. • The Wife thread executes withdraw()’s amount <= balance expression, which returns true. • The Wife thread performs the withdrawal. The scheduler pauses the Wife thread and lets the Husband thread execute. • The Husband thread performs the withdrawal. This problem can be corrected by synchronizing access to withdraw() so that only one thread at a time can execute inside this method. You synchronize access at the method level by adding reserved word synchronized to the method header prior to the method’s return type; for example, synchronized boolean withdraw(int amount). As I demonstrate later, you can also synchronize access to a block of statements by specifying synchronized(object) { /* synchronized statements */ }, where object is an arbitrary object 293 CHAPTER 4 TOURING LANGUAGE APIS reference. No thread can enter a synchronized method or block until execution leaves the method/block; this is known as mutual exclusion. Synchronization is implemented in terms of monitors and locks. A monitor is a concurrency construct for controlling access to a critical section, a region of code that must execute atomically. It is identified at the source code level as a synchronized method or a synchronized block. A lock is a token that a thread must acquire before a monitor allows that thread to execute inside a monitor’s critical section. The token is released automatically when the thread exits the monitor, to give another thread an opportunity to acquire the token and enter the monitor. ■ Note A thread that has acquired a lock does not release this lock when it calls one of Thread’s sleep() methods. A thread entering a synchronized instance method acquires the lock associated with the object on which the method is called. A thread entering a synchronized class method acquires the lock associated with the class’s Class object. Finally, a thread entering a synchronized block acquires the lock associated with the block’s controlling object. ■ Tip Thread declares a static boolean holdsLock(Object o) method that returns true when the calling thread holds the monitor lock on object o. You will find this method handy in assertion statements, such as assert Thread.holdsLock(o);. The need for synchronization is often subtle. For example, Listing 4-23’s ID utility class declares a getNextID() method that returns a unique long-based ID, perhaps to be used when generating unique filenames. Although you might not think so, this method can cause data corruption and return duplicate values. Listing 4-23. A utility class for returning unique IDs class ID { private static long nextID = 0; static long getNextID() { return nextID++; } } There are two lack-of-synchronization problems with getNextID(). Because 32-bit JVM implementations require two steps to update a 64-bit long integer, adding 1 to nextID is not atomic: the scheduler could interrupt a thread that has only updated half of nextID, which corrupts the contents of this variable. 294 CHAPTER 4 TOURING LANGUAGE APIS ■ Note Variables of type long and double are subject to corruption when being written to in an unsynchronized context on 32-bit JVMs. This problem does not occur with variables of type boolean, byte, char, float, int, or short; each type occupies 32 bits or less. Assume that multiple threads call getNextID(). Because postincrement (++) reads and writes the nextID field in two steps, multiple threads might retrieve the same value. For example, thread A executes ++, reading nextID but not incrementing its value before being interrupted by the scheduler. Thread B now executes and reads the same value. Both problems can be corrected by synchronizing access to nextID so that only one thread can execute this method’s code. All that is required is to add synchronized to the method header prior to the method’s return type; for example, static synchronized int getNextID(). Synchronization is also used to communicate between threads. For example, you might design your own mechanism for stopping a thread (because you cannot use Thread’s unsafe stop() methods for this task). Listing 4-24 shows how you might accomplish this task. Listing 4-24. Attempting to stop a thread class ThreadStopping { public static void main(String[] args) { class StoppableThread extends Thread { private boolean stopped = false; @Override public void run() { while(!stopped) System.out.println("running"); } void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } } 295 CHAPTER 4 TOURING LANGUAGE APIS Listing 4-24 introduces a main() method with a local class named StoppableThread that subclasses Thread. StoppableThread declares a stopped field initialized to false, a stopThread() method that sets this field to true, and a run() method whose infinite loop checks stopped on each loop iteration to see if its value has changed to true. After instantiating StoppableThread, the default main thread starts the thread associated with this Thread object. It then sleeps for one second and calls StoppableThread’s stop() method before dying. When you run this application on a single-processor/single-core machine, you will probably observe the application stopping. You might not see this stoppage when the application runs on a multiprocessor machine or a uniprocessor machine with multiple cores. For performance reasons, each processor or core probably has its own cache with its own copy of stopped. When one thread modifies its copy of this field, the other thread’s copy of stopped is not changed. Listing 4-25 refactors Listing 4-24 to guarantee that the application will run correctly on all kinds of machines. Listing 4-25. Guaranteed stoppage on a multiprocessor/multicore machine class ThreadStopping { public static void main(String[] args) { class StoppableThread extends Thread { private boolean stopped = false; @Override public void run() { while(!isStopped()) System.out.println("running"); } synchronized void stopThread() { stopped = true; } private synchronized boolean isStopped() { return stopped; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } } 296 CHAPTER 4 TOURING LANGUAGE APIS Listing 4-25’s stopThread() and isStopped() methods are synchronized to support thread communication (between the default main thread that calls stopThread() and the started thread that executes inside run()). When a thread enters one of these methods, it is guaranteed to access a single shared copy of the stopped field (not a cached copy). Synchronization is necessary to support mutual exclusion or mutual exclusion combined with thread communication. However, there is an alternative to synchronization when the only purpose is to communicate between threads. This alternative is reserved word volatile, which Listing 4-26 demonstrates. Listing 4-26. The volatile alternative to synchronization for thread communication class ThreadStopping { public static void main(String[] args) { class StoppableThread extends Thread { private volatile boolean stopped = false; @Override public void run() { while(!stopped) System.out.println("running"); } void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } } Listing 4-26 declares stopped to be volatile; threads that access this field will always access a single shared copy (not cached copies on multiprocessor/multicore machines). As well as generating code that is less verbose, volatile might offer improved performance over synchronization. When a field is declared volatile, it cannot also be declared final. If you’re depending on the semantics (meaning) of volatility, you still get those from a final field. In his “Java theory and practice: Fixing the Java Memory Model, Part 2” article (http://www.ibm.com/developerworks/library/jjtp03304/), Brian Goetz has this to say about this issue: “The new JMM [Java Memory Model] also seeks to provide a new guarantee of initialization safety—that as long as an object is properly constructed (meaning that a reference to the object is not published before the constructor has completed), then all threads will see the values for its final fields that were set in its constructor, regardless of whether or not 297 CHAPTER 4 TOURING LANGUAGE APIS synchronization is used to pass the reference from one thread to another. Further, any variables that can be reached through a final field of a properly constructed object, such as fields of an object referenced by a final field, are also guaranteed to be visible to other threads as well. This means that if a final field contains a reference to, say, a LinkedList, in addition to the correct value of the reference being visible to other threads, also the contents of that LinkedList at construction time would be visible to other threads without synchronization. The result is a significant strengthening of the meaning of final—that final fields can be safely accessed without synchronization, and that compilers can assume that final fields will not change and can therefore optimize away multiple fetches.” ■ Caution You should only use volatile in the context of thread communication. Also, you can only use this reserved word in the context of field declarations. Although you can declare double and long fields volatile, you should avoid doing so on 32-bit JVMs because it takes two operations to access a double or long variable’s value, and mutual exclusion via synchronization is required to access their values safely. Object’s wait(), notify(), and notifyAll() methods support a form of thread communication where a thread voluntarily waits for some condition (a prerequisite for continued execution) to arise, at which time another thread notifies the waiting thread that it can continue. wait() causes its calling thread to wait on an object’s monitor, and notify() and notifyAll() wake up one or all threads waiting on the monitor. ■ Caution Because the wait(), notify(), and notifyAll() methods depend on a lock, they cannot be called from outside of a synchronized method or synchronized block. If you fail to heed this warning, you will encounter a thrown instance of the java.lang.IllegalMonitorStateException class. Also, a thread that has acquired a lock releases this lock when it calls one of Object’s wait() methods. A classic example of thread communication involving conditions is the relationship between a producer thread and a consumer thread. The producer thread produces data items to be consumed by the consumer thread. Each produced data item is stored in a shared variable. Imagine that the threads are not communicating and are running at different speeds. The producer might produce a new data item and record it in the shared variable before the consumer retrieves the previous data item for processing. Also, the consumer might retrieve the contents of the shared variable before a new data item is produced. To overcome those problems, the producer thread must wait until it is notified that the previously produced data item has been consumed, and the consumer thread must wait until it is notified that a new data item has been produced. Listing 4-27 shows you how to accomplish this task via wait() and notify(). Listing 4-27. The producer-consumer relationship class PC 298 CHAPTER 4 TOURING LANGUAGE APIS { public static void main(String[] args) { Shared s = new Shared(); new Producer(s).start(); new Consumer(s).start(); } } class Shared { private char c = '\u0000'; private boolean writeable = true; synchronized void setSharedChar(char c) { while (!writeable) try { wait(); } catch (InterruptedException e) {} this.c = c; writeable = false; notify(); } synchronized char getSharedChar() { while (writeable) try { wait(); } catch (InterruptedException e) {} writeable = true; notify(); return c; } } class Producer extends Thread { private Shared s; Producer(Shared s) { this.s = s; } @Override public void run() { for (char ch = 'A'; ch <= 'Z'; ch++) { synchronized(s) { s.setSharedChar(ch); 299 CHAPTER 4 TOURING LANGUAGE APIS System.out.println(ch+" produced by producer."); } } } This book was purchased by [email protected] } class Consumer extends Thread { private Shared s; Consumer(Shared s) { this.s = s; } @Override public void run() { char ch; do { synchronized(s) { ch = s.getSharedChar(); System.out.println(ch+" consumed by consumer."); } } while (ch != 'Z'); } } The application creates a Shared object and two threads that get a copy of the object's reference. The producer calls the object's setSharedChar() method to save each of 26 uppercase letters; the consumer calls the object’s getSharedChar() method to acquire each letter. The writeable instance field tracks two conditions: the producer waiting on the consumer to consume a data item, and the consumer waiting on the producer to produce a new data item. It helps coordinate execution of the producer and consumer. The following scenario, where the consumer executes first, illustrates this coordination: 1. The consumer executes s.getSharedChar() to retrieve a letter. 2. Inside of that synchronized method, the consumer calls wait() because writeable contains true. The consumer now waits until it receives notification from the producer. 3. The producer eventually executes s.setSharedChar(ch);. 4. When the producer enters that synchronized method (which is possible because the consumer released the lock inside of the wait() method prior to waiting), the producer discovers writeable’s value to be true and does not call wait(). 5. 300 The producer saves the character, sets writeable to false (which will cause the producer to wait on the next setSharedChar() call when the consumer has not consumed the character by that time), and calls notify() to awaken the consumer (assuming the consumer is waiting). CHAPTER 4 TOURING LANGUAGE APIS 6. The producer exits setSharedChar(char c). 7. The consumer wakes up (and reacquires the lock), sets writeable to true (which will cause the consumer to wait on the next getSharedChar() call when the producer has not produced a character by that time), notifies the producer to awaken that thread (assuming the producer is waiting), and returns the shared character. Although the synchronization works correctly, you might observe output (on some platforms) that shows multiple producing messages before a consuming message. For example, you might see A produced by producer., followed by B produced by producer., followed by A consumed by consumer., at the beginning of the application’s output. This strange output order is caused by the call to setSharedChar() followed by its companion System.out.println() method call not being atomic, and by the call to getSharedChar() followed by its companion System.out.println() method call not being atomic. The output order is corrected by wrapping each of these method call pairs in a synchronized block that synchronizes on the s-referenced Shared object. When you run this application, its output should always appear in the same alternating order, as shown next (only the first few lines are shown for brevity): A A B B C C D D produced consumed produced consumed produced consumed produced consumed by by by by by by by by producer. consumer. producer. consumer. producer. consumer. producer. consumer. ■ Caution Never call wait() outside of a loop. The loop tests the condition (!writeable or writeable in the previous example) before and after the wait() call. Testing the condition before calling wait() ensures liveness. If this test was not present, and if the condition held and notify() had been called prior to wait() being called, it is unlikely that the waiting thread would ever wake up. Retesting the condition after calling wait() ensures safety. If retesting did not occur, and if the condition did not hold after the thread had awakened from the wait() call (perhaps another thread called notify() accidentally when the condition did not hold), the thread would proceed to destroy the lock’s protected invariants. Too much synchronization can be problematic. If you are not careful, you might encounter a situation where locks are acquired by multiple threads, neither thread holds its own lock but holds the lock needed by some other thread, and neither thread can enter and later exit its critical section to release its held lock because some other thread holds the lock to that critical section. Listing 4-28’s atypical example demonstrates this scenario, which is known as deadlock. Listing 4-28. A pathological case of deadlock class Deadlock 301 CHAPTER 4 TOURING LANGUAGE APIS { private Object lock1 = new Object(); private Object lock2 = new Object(); void instanceMethod1() { synchronized(lock1) { synchronized(lock2) { System.out.println("first thread in instanceMethod1"); // critical section guarded first by // lock1 and then by lock2 } } } void instanceMethod2() { synchronized(lock2) { synchronized(lock1) { System.out.println("second thread in instanceMethod2"); // critical section guarded first by // lock2 and then by lock1 } } } public static void main(String[] args) { final Deadlock dl = new Deadlock(); Runnable r1 = new Runnable() { @Override public void run() { while(true) dl.instanceMethod1(); } }; Thread thdA = new Thread(r1); Runnable r2 = new Runnable() { @Override public void run() { while(true) dl.instanceMethod2(); } }; Thread thdB = new Thread(r2); thdA.start(); thdB.start(); 302 CHAPTER 4 TOURING LANGUAGE APIS } } Listing 4-28’s thread A and thread B call instanceMethod1() and instanceMethod2(), respectively, at different times. Consider the following execution sequence: 1. Thread A calls instanceMethod1(), obtains the lock assigned to the lock1referenced object, and enters its outer critical section (but has not yet acquired the lock assigned to the lock2-referenced object). 2. Thread B calls instanceMethod2(), obtains the lock assigned to the lock2referenced object, and enters its outer critical section (but has not yet acquired the lock assigned to the lock1-referenced object). 3. Thread A attempts to acquire the lock associated with lock2. The JVM forces the thread to wait outside of the inner critical section because thread B holds that lock. 4. Thread B attempts to acquire the lock associated with lock1. The JVM forces the thread to wait outside of the inner critical section because thread A holds that lock. 5. Neither thread can proceed because the other thread holds the needed lock. We have a deadlock situation and the program (at least in the context of the two threads) freezes up. Although the previous example clearly identifies a deadlock state, it is often not that easy to detect deadlock. For example, your code might contain the following circular relationship among various classes (in several source files): • Class A’s synchronized method calls class B’s synchronized method. • Class B’s synchronized method calls class C’s synchronized method. • Class C’s synchronized method calls class A’s synchronized method. If thread A calls class A’s synchronized method and thread B calls class C’s synchronized method, thread B will block when it attempts to call class A’s synchronized method and thread A is still inside of that method. Thread A will continue to execute until it calls class C’s synchronized method, and then block. Deadlock results. ■ Note Neither the Java language nor the JVM provides a way to prevent deadlock, and so the burden falls on you. The simplest way to prevent deadlock from happening is to avoid having either a synchronized method or a synchronized block call another synchronized method/block. Although this advice prevents deadlock from happening, it is impractical because one of your synchronized methods/blocks might need to call a synchronized method in a Java API, and the advice is overkill because the synchronized method/block being called might not call any other synchronized method/block, so deadlock would not occur. 303 CHAPTER 4 TOURING LANGUAGE APIS You will sometimes want to associate per-thread data (such a user ID) with a thread. Although you can accomplish this task with a local variable, you can only do so while the local variable exists. You could use an instance field to keep this data around longer, but then you would have to deal with synchronization. Thankfully, Java supplies ThreadLocal as a simple (and very handy) alternative. Each instance of the ThreadLocal class describes a thread-local variable, which is a variable that provides a separate storage slot to each thread that accesses the variable. You can think of a thread-local variable as a multi-slot variable in which each thread can store a different value in the same variable. Each thread sees only its value and is unaware of other threads having their own values in this variable. ThreadLocal is generically declared as ThreadLocal<T>, where T identifies the type of value that is stored in the variable. This class declares the following constructor and methods: • ThreadLocal() creates a new thread-local variable. • T get() returns the value in the calling thread’s storage slot. If an entry does not exist when the thread calls this method, get() calls initialValue(). • T initialValue() creates the calling thread’s storage slot and stores an initial (default) value in this slot. The initial value defaults to null. You must subclass ThreadLocal and override this protected method to provide a more suitable initial value. • void remove() removes the calling thread’s storage slot. If this method is followed by get() with no intervening set(), get() calls initialValue(). • void set(T value) sets the value of the calling thread’s storage slot to value. Listing 4-29 shows you how to use ThreadLocal to associate a different user ID with each of two threads. Listing 4-29. Different user IDs for different threads class ThreadLocalDemo { private static volatile ThreadLocal<String> userID = new ThreadLocal<String>(); public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { String name = Thread.currentThread().getName(); if (name.equals("A")) userID.set("foxtrot"); else userID.set("charlie"); System.out.println(name+" "+userID.get()); } }; Thread thdA = new Thread(r); thdA.setName("A"); Thread thdB = new Thread(r); 304 CHAPTER 4 TOURING LANGUAGE APIS thdB.setName("B"); thdA.start(); thdB.start(); } } After instantiating ThreadLocal and assigning the reference to a volatile class field named userID (the field is volatile because it is accessed by different threads, which might execute on a multiprocessor/multicore machine), the default main thread creates two more threads that store different String objects in userID and output their objects. When you run this application, you will observe the following output (possibly not in this order): A foxtrot B charlie Values stored in thread-local variables are not related. When a new thread is created, it gets a new storage slot containing initialValue()’s value. Perhaps you would prefer to pass a value from a parent thread, a thread that creates another thread, to a child thread, the created thread. You accomplish this task with InheritableThreadLocal. InheritableThreadLocal is a subclass of ThreadLocal. As well as declaring an InheritableThreadLocal() constructor, this class declares the following protected method: • T childValue(T parentValue) calculates the child’s initial value as a function of the parent’s value at the time the child thread is created. This method is called from the parent thread before the child thread is started. The method returns the argument passed to parentValue and should be overridden when another value is desired. Listing 4-30 shows you how to use InheritableThreadLocal to pass a parent thread’s Integer object to a child thread. Listing 4-30. Different user IDs for different threads class InheritableThreadLocalDemo { private static volatile InheritableThreadLocal<Integer> intVal = new InheritableThreadLocal<Integer>(); public static void main(String[] args) { Runnable rP = new Runnable() { @Override public void run() { intVal.set(new Integer(10)); Runnable rC = new Runnable() { public void run() { Thread thd; thd = Thread.currentThread(); String name = thd.getName(); System.out.println(name+" "+ 305 CHAPTER 4 TOURING LANGUAGE APIS intVal.get()); } }; Thread thdChild = new Thread(rC); thdChild.setName("Child"); thdChild.start(); } }; new Thread(rP).start(); } } After instantiating InheritableThreadLocal and assigning it to a volatile class field named intVal, the default main thread creates a parent thread, which stores an Integer object containing 10 in intVal. The parent thread creates a child thread, which accesses intVal and retrieves its parent thread’s Integer object. When you run this application, you will observe the following output: Child 10 BigDecimal Chapter 2 introduced you to a SavingsAccount class with a balance field. I declared this field to be of type int, and mentioned that balance represents the number of dollars that can be withdrawn. Alternatively, I could have stated that balance represents the number of pennies that can be withdrawn. Perhaps you are wondering why I did not declare balance to be of type double or float. That way, balance could store values such as 18.26 (18 dollars in the whole number part and 26 pennies in the fraction part). I did not declare balance to be a double or float for the following reasons: • Not all floating-point values that can represent monetary amounts (dollars and cents) can be stored exactly in memory. For example, 0.1 (which you might use to represent 10 cents) has no exact storage representation. If you executed double total = 0.1; for (int i = 0; i < 50; i++) total += 0.1; System.out.println(total);, you would observe 5.099999999999998 instead of the correct 5.1 as the output. • The result of each floating-point calculation needs to be rounded to the nearest cent. Failure to do so introduces tiny errors that can cause the final result to differ from the correct result. Although Math supplies a pair of round() methods that you might consider using to round a calculation to the nearest cent, these methods round to the nearest integer (dollar). Listing 4-31’s InvoiceCalc application demonstrates both problems. However, the first problem is not serious because it contributes very little to the inaccuracy. The more serious problem occurs from failing to round to the nearest cent after performing a calculation. Listing 4-31. Floating-point-based invoice calculations leading to confusing results import java.text.NumberFormat; class InvoiceCalc { 306 CHAPTER 4 TOURING LANGUAGE APIS final static double DISCOUNT_PERCENT = 0.1; // 10% final static double TAX_PERCENT = 0.05; // 5% public static void main(String[] args) { double invoiceSubtotal = 285.36; double discount = invoiceSubtotal*DISCOUNT_PERCENT; double subtotalBeforeTax = invoiceSubtotal-discount; double salesTax = subtotalBeforeTax*TAX_PERCENT; double invoiceTotal = subtotalBeforeTax+salesTax; NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(); System.out.println("Subtotal: "+currencyFormat.format(invoiceSubtotal)); System.out.println("Discount: "+currencyFormat.format(discount)); System.out.println("SubTotal after discount: "+ currencyFormat.format(subtotalBeforeTax)); System.out.println("Sales Tax: "+currencyFormat.format(salesTax)); System.out.println("Total: "+currencyFormat.format(invoiceTotal)); } } Listing 4-31 relies on the NumberFormat class (located in the java.text package) and its format() method to format a double precision floating-point value into a currency—I discuss NumberFormat in the Internationalization section of Appendix C. When you run InvoiceCalc, you will discover the following output: Subtotal: $285.36 Discount: $28.54 SubTotal after discount: $256.82 Sales Tax: $12.84 Total: $269.67 This output reveals the correct subtotal, discount, subtotal after discount, and sales tax. In contrast, it incorrectly reveals 269.67 instead of 269.66 as the final total. The customer will not appreciate paying an extra penny, even though 269.67 is the correct value according to the floating-point calculations: Subtotal: 285.36 Discount: 28.536 SubTotal after discount: 256.824 Sales Tax: 12.8412 Total: 269.6652 The problem arises from not rounding the result of each calculation to the nearest cent before performing the next calculation. As a result, the 0.024 in 256.824 and 0.0012 in 12.84 contribute to the final value, causing NumberFormat’s format() method to round this value to 269.67. ■ Caution Never using float or double to represent monetary values. Java provides a solution to both problems in the form of a BigDecimal class. This immutable class (a BigDecimal instance cannot be modified) represents a signed decimal number (such as 23.653) of 307 CHAPTER 4 TOURING LANGUAGE APIS arbitrary precision (number of digits) with an associated scale (an integer that specifies the number of digits after the decimal point). BigDecimal declares three convenience constants: ONE, TEN, and ZERO. Each constant is the BigDecimal equivalent of 1, 10, and 0 with a zero scale. ■ Caution BigDecimal declares several ROUND_-prefixed constants. These constants are largely obsolete and should be avoided, along with the BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) and BigDecimal setScale(int newScale, int roundingMode) methods, which are still present so that dependent legacy code continues to compile. BigDecimal also declares a variety of useful constructors and methods. A few of these constructors and methods are described in Table 4-8. Table 4-8. BigDecimal Constructors and Methods Method 308 Description BigDecimal(int val) Initialize the BigDecimal instance to val’s digits. Set the scale to 0. BigDecimal(String val) Initialize the BigDecimal instance to the decimal equivalent of val. Set the scale to the number of digits after the decimal point, or 0 if no decimal point is specified. This constructor throws NullPointerException when val is null, and NumberFormatException when val’s string representation is invalid (contains letters, for example). BigDecimal abs() Return a new BigDecimal instance that contains the absolute value of the current instance’s value. The resulting scale is the same as the current instance’s scale. BigDecimal add(BigDecimal augend) Return a new BigDecimal instance that contains the sum of the current value and the argument value. The resulting scale is the maximum of the current and argument scales. This method throws NullPointerException when augend is null. BigDecimal divide(BigDecimal divisor) Return a new BigDecimal instance that contains the quotient of the current value divided by the argument value. The resulting scale is the difference of the current and argument scales. It might be adjusted when the result requires more digits. This method throws NullPointerException when divisor is null, or ArithmeticException when divisor represents 0 or the result cannot be represented exactly. CHAPTER 4 TOURING LANGUAGE APIS BigDecimal max(BigDecimal val) Return either this or val, whichever BigDecimal instance contains the larger value. This method throws NullPointerException when val is null. BigDecimal min(BigDecimal val) Return either this or val, whichever BigDecimal instance contains the smaller value. This method throws NullPointerException when val is null. BigDecimal multiply(BigDecimal multiplicand) Return a new BigDecimal instance that contains the product of the current value and the argument value. The resulting scale is the sum of the current and argument scales. This method throws NullPointerException when multiplicand is null. BigDecimal negate() Return a new BigDecimal instance that contains the negative of the current value. The resulting scale is the same as the current scale. int precision() Return the precision of the current BigDecimal instance. BigDecimal remainder(BigDecimal divisor) Return a new BigDecimal instance that contains the remainder of the current value divided by the argument value. The resulting scale is the difference of the current scale and the argument scale. It might be adjusted when the result requires more digits. This method throws NullPointerException when divisor is null, or ArithmeticException when divisor represents 0. int scale() Return the scale of the current BigDecimal instance. BigDecimal setScale(int newScale, RoundingMode roundingMode) Return a new BigDecimal instance with the specified scale and rounding mode. If the new scale is greater than the old scale, additional zeros are added to the unscaled value. In this case no rounding is necessary. If the new scale is smaller than the old scale, trailing digits are removed. If these trailing digits are not zero, the remaining unscaled value has to be rounded. For this rounding operation, the specified rounding mode is used. This method throws NullPointerException when roundingMode is null, and ArithmeticException when roundingMode is set to RoundingMode.ROUND_UNNECESSARY but rounding is necessary based on the current scale. BigDecimal subtract(BigDecimal subtrahend) Return a new BigDecimal instance that contains the current value minus the argument value. The resulting scale is the maximum of the current and argument scales. This method throws NullPointerException when subtrahend is null. 309 CHAPTER 4 TOURING LANGUAGE APIS String toString() Return a string representation of this BigDecimal instance. Scientific notation is used when necessary. Table 4-8 refers to java.math.RoundingMode, which is an enum containing various rounding mode constants. These constants are described in Table 4-9. This book was purchased by [email protected] Table 4-9. RoundingMode Constants Constant Description CEILING Round toward positive infinity. DOWN Round toward zero. FLOOR Round toward negative infinity. HALF_DOWN Round toward the “nearest neighbor” unless both neighbors are equidistant, in which case round down. HALF_EVEN Round toward the “nearest neighbor” unless both neighbors are equidistant, in which case, round toward the even neighbor. HALF_UP Round toward “nearest neighbor” unless both neighbors are equidistant, in which case round up. (This is the rounding mode commonly taught at school.) UNNECESSARY Rounding is not necessary because the requested operation produces the exact result. UP Positive values are rounded toward positive infinity and negative values are rounded toward negative infinity. The best way to get comfortable with BigDecimal is to try it out. Listing 4-32 uses this class to correctly perform the invoice calculations that were presented in Listing 4-31. Listing 4-32. BigDecimal-based invoice calculations not leading to confusing results import java.math.BigDecimal; import java.math.RoundingMode; class InvoiceCalc { public static void main(String[] args) { BigDecimal invoiceSubtotal = new BigDecimal("285.36"); BigDecimal discountPercent = new BigDecimal("0.10"); BigDecimal discount = invoiceSubtotal.multiply(discountPercent); 310 CHAPTER 4 TOURING LANGUAGE APIS discount = discount.setScale(2, RoundingMode.HALF_UP); BigDecimal subtotalBeforeTax = invoiceSubtotal.subtract(discount); subtotalBeforeTax = subtotalBeforeTax.setScale(2, RoundingMode.HALF_UP); BigDecimal salesTaxPercent = new BigDecimal("0.05"); BigDecimal salesTax = subtotalBeforeTax.multiply(salesTaxPercent); salesTax = salesTax.setScale(2, RoundingMode.HALF_UP); BigDecimal invoiceTotal = subtotalBeforeTax.add(salesTax); invoiceTotal = invoiceTotal.setScale(2, RoundingMode.HALF_UP); System.out.println("Subtotal: "+invoiceSubtotal); System.out.println("Discount: "+discount); System.out.println("SubTotal after discount: "+subtotalBeforeTax); System.out.println("Sales Tax: "+salesTax); System.out.println("Total: "+invoiceTotal); } } Listing 4-32’s main() method first creates BigDecimal objects invoiceSubtotal and discountPercent that are initialized to 285.36 and 0.10, respectively. It then multiplies invoiceSubtotal by discountPercent and assigns the BigDecimal result to discount. At this point, discount contains 28.5360. Apart from the trailing zero, this value is the same as that generated by invoiceSubtotal*DISCOUNT_PERCENT in Listing 4-31. The value that should be stored in discount is 28.54. To correct this problem before performing another calculation, main() calls discount’s setScale() method with these arguments: • 2: Two digits after the decimal point • RoundingMode.HALF_UP: The conventional approach to rounding After setting the scale and proper rounding mode, main() subtracts discount from invoiceSubtotal, and assigns the resulting BigDecimal instance to subtotalBeforeTax. main() calls setScale() on subtotalBeforeTax to properly round its value before moving on to the next calculation. main() next creates a BigDecimal object named salesTaxPercent that is initialized to 0.05. It then multiplies subtotalBeforeTax by salesTaxPercent, assigning the result to salesTax, and calls setScale() on this BigDecimal object to properly round its value. Moving on, main() adds salesTax to subtotalBeforeTax, saving the result in invoiceTotal, and rounds the result via setScale(). The values in these objects are sent to the standard output device via System.out.println(), which calls their toString() methods to return string representations of the BigDecimal values. When you run this new version of InvoiceCalc, you will discover the following output: Subtotal: 285.36 Discount: 28.54 SubTotal after discount: 256.82 Sales Tax: 12.84 Total: 269.66 ■ Caution BigDecimal declares a BigDecimal(double val) constructor that you should avoid using if at all possible. This constructor initializes the BigDecimal instance to the value stored in val, making it possible for this instance to reflect an invalid representation when the double value cannot be stored exactly. For example, 311 CHAPTER 4 TOURING LANGUAGE APIS BigDecimal(0.1) results in 0.1000000000000000055511151231257827021181583404541015625 being stored in the instance. In contrast, BigDecimal("0.1") stores 0.1 exactly. BigInteger BigDecimal stores a signed decimal number as an unscaled value with a 32-bit integer scale. The unscaled value is stored in an instance of the BigInteger class. BigInteger is an immutable class that represents a signed integer of arbitrary precision. It stores its value in two’s complement format (all bits are flipped—1s to 0s and 0s to 1s—and 1 is added to the result to be compatible with the two’s complement format used by Java’s byte integer, short integer, integer, and long integer types). ■ Note Check out Wikipedia’s “Two’s complement” entry (http://en.wikipedia.org/wiki/Two's_complement) to learn more about two’s complement. BigInteger declares three convenience constants: ONE, TEN, and ZERO. Each constant is the BigInteger equivalent of 1, 10, and 0. BigInteger also declares a variety of useful constructors and methods. A few of these constructors and methods are described in Table 4-10. Table 4-10. BigInteger Constructors and Methods 312 Method Description BigInteger(byte[] val) Initialize the BigInteger instance to the integer that is stored in the val array, with val[0] storing the integer’s most significant (leftmost) eight bits. This constructor throws NullPointerException when val is null, and NumberFormatException when val.length equals 0. BigInteger(String val) Initialize the BigInteger instance to the integer equivalent of val. This constructor throws NullPointerException when val is null, and NumberFormatException when val’s string representation is invalid (contains letters, for example). BigInteger abs() Return a new BigInteger instance that contains the absolute value of the current instance’s value. BigInteger add(BigInteger augend) Return a new BigInteger instance that contains the sum of the current value and the argument value. This method throws NullPointerException when augend is null. CHAPTER 4 TOURING LANGUAGE APIS BigInteger divide(BigInteger divisor) Return a new BigInteger instance that contains the quotient of the current value divided by the argument value. This method throws NullPointerException when divisor is null, and ArithmeticException when divisor represents 0 or the result cannot be represented exactly. BigInteger max(BigInteger val) Return either this or val, whichever BigInteger instance contains the larger value. This method throws NullPointerException when val is null. BigInteger min(BigInteger val) Return either this or val, whichever BigInteger instance contains the smaller value. This method throws NullPointerException when val is null. BigInteger multiply(BigInteger multiplicand) Return a new BigInteger instance that contains the product of the current value and the argument value. This method throws NullPointerException when multiplicand is null. BigInteger negate() Return a new BigInteger instance that contains the negative of the current value. BigInteger remainder(BigInteger divisor) Return a new BigInteger instance that contains the remainder of the current value divided by the argument value. This method throws NullPointerException when divisor is null, and ArithmeticException when divisor represents 0. BigInteger subtract(BigInteger subtrahend) Return a new BigInteger instance that contains the current value minus the argument value. This method throws NullPointerException when subtrahend is null. String toString() Return a string representation of this BigInteger. ■ Note BigInteger also declares several bit-oriented methods, such as BigInteger and (BigInteger val), BigInteger flipBit(int n), and BigInteger shiftLeft(int n). These methods are useful for when you need to perform low-level bit manipulation. The best way to get comfortable with BigInteger is to try it out. Listing 4-33 uses this class in a factorial() method comparison context. Listing 4-33. Comparing factorial() methods import java.math.BigInteger; 313 CHAPTER 4 TOURING LANGUAGE APIS class FactComp { public static void main(String[] args) { System.out.println(factorial(12)); System.out.println(); System.out.println(factorial(20L)); System.out.println(); System.out.println(factorial(170.0)); System.out.println(); System.out.println(factorial(new BigInteger("170"))); System.out.println(); System.out.println(factorial(25.0)); System.out.println(); System.out.println(factorial(new BigInteger("25"))); } static int factorial(int n) { if (n == 0) return 1; else return n*factorial(n-1); } static long factorial(long n) { if (n == 0) return 1; else return n*factorial(n-1); } static double factorial(double n) { if (n == 1.0) return 1.0; else return n*factorial(n-1); } static BigInteger factorial(BigInteger n) { if (n.equals(BigInteger.ZERO)) return BigInteger.ONE; else return n.multiply(factorial(n.subtract(BigInteger.ONE))); } } Listing 4-33 compares four versions of the recursive factorial() method. This comparison reveals the largest argument that can be passed to each of the first three methods before the returned factorial value becomes meaningless, because of limits on the range of values that can be accurately represented by the numeric type. 314 CHAPTER 4 TOURING LANGUAGE APIS The first version is based on int and has a useful argument range of 0 through 12. Passing any argument greater than 12 results in a factorial that cannot be represented accurately as an int. You can increase the useful range of factorial(), but not by much, by changing the parameter and return types to long. After making these changes, you will discover that the upper limit of the useful range is 20. To further increase the useful range, you might create a version of factorial() whose parameter and return types are double. This is possible because whole numbers can be represented exactly as doubles. However, the largest useful argument that can be passed is 170.0. Anything higher than this value results in factorial() returning +infinity. It is possible that you might need to calculate a higher factorial value, perhaps in the context of calculating a statistics problem involving combinations or permutations. The only way to accurately calculate this value is to use a version of factorial() based on BigInteger. When you run this application, as in java FactComp, it generates the following output: 479001600 2432902008176640000 7.257415615307994E306 7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242 6174595114909912378385207766660225654427530253289007732075109024004302800582956039666125996582 5710439855829425756896631343961226257109494680671120556888045719334021266145280000000000000000 0000000000000000000000000 1.5511210043330986E25 15511210043330985984000000 The first three values represent the highest factorials that can be returned by the int-based, longbased, and double-based factorial() methods. The fourth value represents the BigInteger equivalent of the highest double factorial. Notice that the double method fails to accurately represent 170! (! is the math symbol for factorial). Its precision is simply too small. Although the method attempts to round the smallest digit, rounding does not always work—the number ends in 7994 instead of 7998. Rounding is only accurate up to argument 25.0, as the last two output lines reveal. ■ Note RSA encryption, BigDecimal, and factorial are practical examples of BigInteger’s usefulness. However, you can also use BigInteger in unusual ways. For example, my February 2006 JavaWorld article titled “Travel Through Time with Java” (http://www.javaworld.com/javaworld/jw-02-2006/jw-0213-funandgames.html), a part of my Java Fun and Games series, used BigInteger to store an image as a very large integer. The idea was to experiment with BigInteger methods to look for images (possibly by discovering mathematical patterns) of people and places that existed in the past, will exist in the future, or might never exist. If this craziness appeals to you, check out my article. 315 CHAPTER 4 TOURING LANGUAGE APIS EXERCISES The following exercises are designed to test your understanding of Java’s language APIs: 316 1. A prime number is a positive integer greater than 1 that is evenly divisible by 1 and itself. Create a PrimeNumberTest application that determines if its solitary integer argument is prime or not prime, and outputs a suitable message. For example, java PrimeNumberTest 289 should output the message 289 is not prime. A simple way to check for primality is to loop from 2 through the square root of the integer argument, and use the remainder operator in the loop to determine if the argument is divided evenly by the loop index. For example, because 6/2 yields a remainder of 0 (2 divides evenly into 6), integer 6 is not a prime number. 2. Reflection is useful in a device driver context, where an application needs to interact with different versions of a driver. If an older version is detected, the application invokes its methods. If a newer version is detected, the application can invoke the older methods or invoke newer versions of those methods. Create two versions of a Driver class. The first version declares a String getCapabilities() method that returns "basic capabilities", and the second version declares this method along with a String getCapabilitiesEx() method that returns "extended capabilities". Create a DriverDemo class that uses reflection to determine if the current Driver.class classfile supports getCapabilitiesEx(), and invoke that method if it does. If the method does not exist, use reflection to determine if it supports getCapabilities(), and invoke that method if that is the case. Otherwise, output an error message. 3. Java arrays have fixed lengths. Create a growable array class, GArray<E>, whose instances store objects of the type specified by the actual type argument passed to E. This class declares a GArray(int initCapacity) constructor that creates an internal array with the number of elements specified by initCapacity. Also, this class declares E get(int index) and void set(int index, E value) methods that respectively return the object at the index position within the internal array, and store the specified value in the array at the index position. The get() method must throw ArrayIndexOutOfBoundsException when the argument passed to index is out of range (negative or greater than/equal to the array’s length). The set() method must throw the same exception when the argument passed to index is negative. However, when the argument is positive, it must create a new internal array whose size is twice that of the old array, copy elements from the old array to the new array via System.arraycopy(), and store the new value at the index position. This class also declares an int size() method that returns the array’s size. Test this class with the GArrayDemo application described in Listing 4-34. CHAPTER 4 TOURING LANGUAGE APIS Listing 4-34. Demonstrating a growable array import ca.tutortutor.collections.GArray; class GArrayDemo { public static void main(String[] args) { GArray<String> ga = new GArray<>(10); System.out.println("Size = "+ga.size()); ga.set(3, "ABC"); System.out.println("Size = "+ga.size()); ga.set(22, "XYZ"); System.out.println("Size = "+ga.size()); System.out.println(ga.get(3)); System.out.println(ga.get(22)); System.out.println(ga.get(20)); ga.set(20, "PQR"); System.out.println(ga.get(20)); System.out.println("Size = "+ga.size()); } } When you run this application, it should generate the following output: Size Size Size ABC XYZ null PQR Size = 0 = 4 = 23 = 23 4. Modify Listing 4-17’s CountingThreads application by marking the two counting threads as daemon threads. What happens when you run the resulting application? 5. Modify Listing 4-17’s CountingThreads application by adding logic to stop both counting threads when the user presses the Enter key. The default main thread should call System.in.read() prior to terminating, and assign true to a variable named stopped after this method call returns. Each counting thread should test this variable to see if it contains true at the start of each loop iteration, and only continue the loop when the variable contains false. Summary Java’s standard class library provides various language-oriented APIs via the java.lang and java.math packages. These APIs include Math and StrictMath, Package, Primitive Type Wrapper Class, Reference, Reflection, String, StringBuffer and StringBuilder, System, Threading, BigDecimal, and BigInteger. 317 CHAPTER 4 TOURING LANGUAGE APIS The Math and StrictMath classes offer a wide variety of useful math-oriented methods for calculating trigonometric values, generating pseudorandom numbers, and so on. StrictMath differs from Math by ensuring that all of these mathematical operations yield the same results on all platforms. The Package class provides access to package information. This information includes version details about the implementation and specification of a Java package, the package’s name, and an indication of whether the package is sealed or not. Instances of the Boolean, Byte, Character, Double, Float, Integer, Long, and Short primitive type wrapper classes wrap themselves around values of primitive types. These classes are useful for storing primitive values in collections, and for providing a good place to associate useful constants (such as MAX_VALUE and MIN_VALUE) and class methods (such as Integer’s parseInt() methods and Character’s isDigit(), isLetter(), and toUpperCase() methods) with the primitive types. The Reference API makes it possible for an application to interact with the garbage collector in limited ways. This API consists of classes Reference, ReferenceQueue, SoftReference, WeakReference, and PhantomReference. SoftReference is useful for implementing image caches, WeakReference is useful for preventing memory leaks related to hashmaps, and PhantomReference is useful for learning when an object has been finalized so that its resources can be cleaned up. The Reflection API lets applications learn about loaded classes, interfaces, enums (a kind of class), and annotation types (a kind of interface). It also lets applications load classes dynamically, instantiate them, find a class’s fields and methods, access fields, call methods, and perform other tasks reflectively. The entry point into the Reflection API is a special class named Class. Additional classes include Constructor, Field, Method, AccessibleObject, and Array. The String class represents a string as a sequence of characters. Because instances of this class are immutable, Java provides StringBuffer and StringBuilder for building a string more efficiently. The former class can be used in a multithreaded context, whereas the latter class is more performant. The System class provides access to standard input, standard output, standard error, and other system-oriented resources. For example, System provides the arraycopy() method as the fastest portable way to copy one array to another. Java supports threads via its low-level Threading API. This API consists of one interface (Runnable) and four classes (Thread, ThreadGroup, ThreadLocal, and InheritableThreadLocal). Throughout its execution, each thread is isolated from other threads because it has been given its own method-call stack. However, threads can still interfere with each other when they access and manipulate shared data. This interference can corrupt the shared data, and this corruption can cause an application to fail. Java provides a thread-synchronization mechanism to prevent this interference. Money must never be represented by floating-point and double precision floating-point variables because not all monetary values can be represented exactly. In contrast, the BigDecimal class lets you accurately represent and manipulate these values. BigDecimal relies on the BigInteger class for representing its unscaled value. A BigInteger instance describes an integer value that can be of arbitrary length (subject to the limits of the JVM’s memory). This chapter briefly referred to the Collections Framework while introducing the Primitive Type Wrapper Class API. Chapter 5 introduces you to this broad utility API for collecting objects. 318 CHAPTER 5 Collecting Objects Applications often must manage collections of objects. Although you can use arrays for this purpose, they are not always a good choice. For example, arrays have fixed sizes, making it tricky to determine an optimal size when you need to store a variable number of objects. Also, arrays can be indexed by integers only, which make them unsuitable for mapping arbitrary objects to other objects. Java’s standard class library provides the Collections Framework and legacy APIs to manage collections on behalf of applications. Chapter 5 first presents this framework, and then introduces you to these legacy APIs (in case you encounter them in legacy code). Because the framework and legacy APIs may not satisfy specific needs, this chapter lastly focuses on creating special-purpose collections APIs. ■ Note Java’s concurrency utilities (discussed in Chapter 6) extend the Collections Framework. The Collections Framework The Collections Framework is a standard architecture for representing and manipulating collections, which are groups of objects stored in instances of classes designed for this purpose. After presenting an overview of this framework’s architecture, this section introduces you to the various types (mainly located in the java.util package) that contribute to this architecture. Architecture Overview The Collection Framework’s architecture is divided into three sections: • Core interfaces: The framework provides core interfaces for manipulating collections independently of their implementations. • Implementation classes: The framework provides classes that provide different core interface implementations to address performance and other requirements. • Utility classes: The framework provides utility classes whose methods let you sort arrays, obtain synchronized collections, and perform other operations. The core interfaces include java.lang.Iterable, Collection, List, Set, SortedSet, NavigableSet, Queue, Deque, Map, SortedMap, and NavigableMap. Collection extends Iterable; List, Set, and Queue each extend Collection; SortedSet extends Set; NavigableSet extends SortedSet; Deque extends Queue; SortedMap extends Map; and NavigableMap extends SortedMap. 319 CHAPTER 5 COLLECTING OBJECTS Figure 5-1 illustrates the core interfaces hierarchy (arrows point to parent interfaces). This book was purchased by [email protected] Figure 5-1. The Collections Framework is based on a hierarchy of core interfaces. The framework’s implementation classes include ArrayList, LinkedList, TreeSet, HashSet, LinkedHashSet, EnumSet, PriorityQueue, ArrayDeque, TreeMap, HashMap, LinkedHashMap, IdentityHashMap, WeakHashMap, and EnumMap. The name of each concrete class ends in a core interface name, identifying the core interface on which it is based. ■ Note Additional implementation classes are part of the concurrency utilities. The framework’s implementation classes also include the abstract AbstractCollection, AbstractList, AbstractSequentialList, AbstractSet, AbstractQueue, and AbstractMap classes. These classes offer skeletal implementations of the core interfaces to facilitate the creation of concrete implementation classes. Finally, the framework provides two utility classes: Arrays and Collections. Comparable Versus Comparator A collection implementation stores its elements in some order (arrangement). This order may be unsorted, or it may be sorted according to some criterion (such as alphabetical, numerical, or chronological). A sorted collection implementation defaults to storing its elements according to their natural ordering. For example, the natural ordering of String objects is lexicographic or dictionary (also known as alphabetical) order. A collection cannot rely on equals() to dictate natural ordering because this method can only determine if two elements are equivalent. Instead, element classes must implement the java.lang.Comparable<T> interface and its int compareTo(T o) method. 320 CHAPTER 5 COLLECTING OBJECTS ■ Note According to Comparable’s Java documentation, this interface is considered to be part of the Collections Framework, even though it is a member of the java.lang package. A sorted collection uses compareTo() to determine the natural ordering of this method’s element argument o in a collection. compareTo() compares argument o with the current element (which is the element on which compareTo() was called) and does the following: • It returns a negative value when the current element should precede o. • It returns a zero value when the current element and o are the same. • It returns a positive value when the current element should succeed o. When you need to implement Comparable’s compareTo() method, there are some rules that you must follow. These rules, listed next, are similar to those shown in Chapter 2 for implementing the equals() method: • compareTo() must be reflexive: For any nonnull reference value x, x.compareTo(x) must return 0. • compareTo() must be symmetric: For any nonnull reference values x and y, x.compareTo(y) == -y.compareTo(x) must hold. • compareTo() must be transitive: For any nonnull reference values x, y, and z, if x.compareTo(y) > 0 is true, and if y.compareTo(z) > 0 is true, then x.compareTo(z) > 0 must also be true. Also, compareTo() should throw NullPointerException when the null reference is passed to this method. However, you do not need to check for null because this method throws NullPointerException when it attempts to access a null reference’s members. ■ Note Before Java 5 and its introduction of generics, compareTo()’s argument was of type java.lang.Object and had to be cast to the appropriate type before the comparison could be made. The cast operator would throw a java.lang.ClassCastException instance when the argument’s type was not compatible with the cast. You might occasionally need to store in a collection objects that are sorted in some order that differs from their natural ordering. In this case, you would supply a comparator to provide that ordering. A comparator is an object whose class implements the Comparator interface. This interface, whose generic type is Comparator<T>, provides the following pair of methods: • int compare(T o1, T o2) compares both arguments for order. This method returns 0 when o1 equals o2, a negative value when o1 is less than o2, and a positive value when o1 is greater than o2. 321 CHAPTER 5 COLLECTING OBJECTS • boolean equals(Object o) returns true when o “equals” this Comparator in that o is also a Comparator and imposes the same ordering. Otherwise, this method returns false. ■ Note Comparator declares equals() because this interface places an extra condition on this method’s contract. Additionally, this method can return true only if the specified object is also a comparator and it imposes the same ordering as this comparator. You do not have to override Object’s equals() method, but doing so may improve performance by allowing programs to determine that two distinct comparators impose the same order. Chapter 3 provided an example that illustrated implementing Comparable, and you will discover another example later in this chapter. Also, this chapter will present examples of implementing Comparator. Iterable and Collection Most of the core interfaces are rooted in Iterable and its Collection subinterface. Their generic types are Iterable<T> and Collection<E>. Iterable describes any object that can return its contained objects in some sequence. This interface declares an Iterator<T> iterator() method that returns an Iterator instance for iterating over all the contained objects. Collection represents a collection of objects that are known as elements. This interface provides methods that are common to the Collection subinterfaces on which many collections are based. Table 5-1 describes these methods. Table 5-1. Collection Methods Method Description boolean add(E e) Add element e to this collection. Return true if this collection was modified as a result; otherwise, return false. (Attempting to add e to a collection that does not permit duplicates and already contains a same-valued element results in e not being added.) This method throws java.lang.UnsupportedOperationException when add() is not supported, ClassCastException when e’s class is not appropriate for this collection, java.lang.IllegalArgumentException when some property of e prevents it from being added to this collection, java.lang.NullPointerException when e contains the null reference and this collection does not support null elements, and java.lang.IllegalStateException when the element cannot be added at this time because of insertion restrictions. IllegalStateException signals that a method has been 322 CHAPTER 5 COLLECTING OBJECTS invoked at an illegal or inappropriate time. In other words, the Java environment or Java application is not in an appropriate state for the requested operation. It is often thrown when you try to add an element to a bounded queue (a queue with a maximum length) and the queue is full. boolean addAll(Collection<? extends E> c) Add all elements of collection c to this collection. Return true if this collection was modified as the result; otherwise, return false. This method throws UnsupportedOperationException when this collection does not support addAll(), ClassCastException when the class of one of c’s elements is inappropriate for this collection, IllegalArgumentException when some property of an element prevents it from being added to this collection, NullPointerException when c contains the null reference or when one of its elements is null and this collection does not support null elements, and IllegalStateException when not all the elements can be added at this time because of insertion restrictions. void clear() Remove all elements from this collection. This method throws UnsupportedOperationException when this collection does not support clear(). boolean contains(Object o) Return true when this collection contains o; otherwise, return false. This method throws ClassCastException when the class of o is inappropriate for this collection, and NullPointerException when o contains the null reference and this collection does not support null elements. boolean containsAll(Collection<?> c) Return true when this collection contains all the elements that are contained in the collection specified by c; otherwise, return false. This method throws ClassCastException when the class of one of c’s elements is inappropriate for this collection, and NullPointerException when c contains the null reference or when one of its elements is null and this collection does not support null elements. boolean equals(Object o) Compare o with this collection and return true when o equals this collection; otherwise, return false. int hashCode() Return this collection’s hash code. Equal collections have equal hash codes. boolean isEmpty() Return true when this collection contains no elements; otherwise, return false. Iterator<E> iterator() Return an Iterator instance for iterating over all of the elements contained in this collection. There are no 323 CHAPTER 5 COLLECTING OBJECTS guarantees concerning the order in which the elements are returned (unless this collection is an instance of some class that provides a guarantee). This Iterable method is redeclared in Collection for convenience. boolean remove(Object o) Remove the element identified as o from this collection. Return true when the element is removed; otherwise, return false. This method throws UnsupportedOperationException when this collection does not support remove(), ClassCastException when the class of o is inappropriate for this collection, and NullPointerException when o contains the null reference and this collection does not support null elements. boolean removeAll(Collection<?> c) Remove all the elements from this collection that are also contained in collection c. Return true when this collection is modified by this operation; otherwise, return false. This method throws UnsupportedOperationException when this collection does not support removeAll(), ClassCastException when the class of one of c’s elements is inappropriate for this collection, and NullPointerException when c contains the null reference or when one of its elements is null and this collection does not support null elements. boolean retainAll(Collection<?> c) Retain all the elements in this collection that are also contained in collection c. Return true when this collection is modified by this operation; otherwise, return false. This method throws UnsupportedOperationException when this collection does not support retainAll(), ClassCastException when the class of one of c’s elements is inappropriate for this collection, and NullPointerException when c contains the null reference or when one of its elements is null and this collection does not support null elements. int size() Return the number of elements contained in this collection, or java.lang.Integer.MAX_VALUE when there are more than Integer.MAX_VALUE elements contained in the collection. Object[] toArray() Return an array containing all the elements stored in this collection. If this collection makes any guarantees as to what order its elements are returned in by its iterator, this method returns the elements in the same order. The returned array is “safe” in that no references to it are maintained by this collection. (In other words, this method allocates a new array even when this collection is backed by an array.) The caller can safely modify the returned array. 324 CHAPTER 5 COLLECTING OBJECTS <T> T[] toArray(T[] a) Return an array containing all the elements in this collection; the runtime type of the returned array is that of the specified array. If the collection fits in the specified array, it is returned in the array. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this collection. This method throws NullPointerException when null is passed to a, and java.lang.ArrayStoreException when a’s runtime type is not a supertype of the runtime type of every element in this collection. Table 5-1 reveals three exceptional things about various Collection methods. First, some methods can throw instances of the UnsupportedOperationException class. For example, add() throws UnsupportedOperationException when you attempt to add an object to an immutable (unmodifiable) collection (discussed later in this chapter). Second, some of Collection’s methods can throw instances of the ClassCastException class. For example, remove() throws ClassCastException when you attempt to remove an entry (also known as mapping) from a tree-based map whose keys are Strings, but specify a non-String key instead. Finally, Collection’s add() and addAll() methods throw IllegalArgumentException instances when some property (attribute) of the element to be added prevents it from being added to this collection. For example, a third-party collection class’s add() and addAll() methods might throw this exception when they detect negative Integer values. ■ Note Perhaps you are wondering why remove() is declared to accept any Object argument instead of accepting only objects whose types are those of the collection. In other words, why is remove() not declared as boolean remove(E e)? Also, why are containsAll(), removeAll(), and retainAll() not declared with an argument of type Collection<? extends E>, to ensure that the collection argument only contains elements of the same type as the collection on which these methods are called? The answer to these questions is the need to maintain backward compatibility. The Collections Framework was introduced before Java 5 and its introduction of generics. To let legacy code written before version 5 continue to compile, these four methods were declared with weaker type constraints. Iterator and the Enhanced For Statement By extending Iterable, Collection inherits that interface’s iterator() method, which makes it possible to iterate over a collection. iterator() returns an instance of a class that implements the Iterator interface, whose generic type is expressed as Iterator<E> and which declares the following three methods: • boolean hasNext() returns true when this Iterator instance has more elements to return; otherwise, this method returns false. 325 CHAPTER 5 COLLECTING OBJECTS • E next() returns the next element from the collection associated with this Iterator instance, or throws java.util.NoSuchElementException when there are no more elements to return. • void remove() removes the last element returned by next() from the collection associated with this Iterator instance. This method can be called only once per next() call. The behavior of an Iterator instance is unspecified when the underlying collection is modified while iteration is in progress in any way other than by calling remove(). This method throws UnsupportedOperationException when it is not supported by this Iterator, and IllegalStateException when remove() has been called without a previous call to next() or when multiple remove() calls occur with no intervening next() calls. The following example shows you how to iterate over a collection after calling iterator() to return an Iterator instance: Collection<String> col = ... // This code does not compile because of the “...”. // Add elements to col. Iterator iter = col.iterator(); while (iter.hasNext()) System.out.println(iter.next()); The while loop repeatedly calls the iterator’s hasNext() method to determine whether or not iteration should continue, and (if it should continue) the next() method to return the next element from the associated collection. Because this idiom is commonly used, Java 5 introduced syntactic sugar to the for statement to simplify iteration in terms of the idiom. This sugar makes this statement appear like the foreach statement found in languages such as Perl, and is revealed in the following simplified equivalent of the previous example: Collection<String> col = ... // This code does not compile because of the “...”. // Add elements to col. for (String s: col) System.out.println(s); This sugar hides col.iterator(), a method call that returns an Iterator instance for iterating over col’s elements. It also hides calls to Iterator’s hasNext() and next() methods on this instance. You interpret this sugar to read as follows: “for each String object in col, assign this object to s at the start of the loop iteration.” ■ Note The enhanced for statement is also useful in an arrays context, in which it hides the array index variable. Consider the following example: String[] verbs = { "run", "walk", "jump" }; for (String verb: verbs) System.out.println(verb); 326 CHAPTER 5 COLLECTING OBJECTS This example, which reads as “for each String object in the verbs array, assign that object to verb at the start of the loop iteration,” is equivalent to the following example: String[] verbs = { "run", "walk", "jump" }; for (int i = 0; i < verbs.length; i++) System.out.println(verbs[i]); The enhanced for statement is limited in that you cannot use this statement where access to the iterator is required to remove an element from a collection. Also, it is not usable where you must replace elements in a collection/array during a traversal, and it cannot be used where you must iterate over multiple collections or arrays in parallel. ■ Tip To have your classes support the enhanced for statement, design these classes to implement the java.lang.Iterable interface. Autoboxing and Unboxing Developers who believe that Java should support only reference types have complained about Java’s support for primitive types. One area where the dichotomy of Java’s type system is clearly seen is the Collections Framework: you can store objects but not primitive type-based values in collections. Although you cannot directly store a primitive type-based value in a collection, you can indirectly store this value by first wrapping it in an object created from a primitive type wrapper class (see Chapter 4) such as Integer, and then storing this primitive type wrapper class instance in the collection—see the following example: Collection<Integer> col = ...; // This code does not compile because of the “...”. int x = 27; col.add(new Integer(x)); // Indirectly store int value 27 via an Integer object. The reverse situation is also tedious. When you want to retrieve the int from col, you must invoke Integer’s intValue() method (which, if you recall, is inherited from Integer’s java.lang.Number superclass). Continuing on from this example, you would specify int y = col.iterator().next().intValue(); to assign the stored 32-bit integer to y. To alleviate this tedium, Java 5 introduced autoboxing and unboxing, which are a pair of complementary syntactic sugar-based language features that make primitive values appear more like objects. (This “sleight of hand” is not complete because you cannot specify expressions such as 27.doubleValue().) Autoboxing automatically boxes (wraps) a primitive value in an object of the appropriate primitive type wrapper class whenever a primitive type is specified but a reference is required. For example, you could change the example’s third line to col.add(x); and have the compiler box x into an Integer object. 327 CHAPTER 5 COLLECTING OBJECTS Unboxing automatically unboxes (unwraps) a primitive value from its wrapper object whenever a reference is specified but a primitive type is required. For example, you could specify int y = col.iterator().next(); and have the compiler unbox the returned Integer object to int value 27 prior to the assignment. Although autoboxing and unboxing were introduced to simplify working with primitive values in a collections context, these language features can be used in other contexts, and this arbitrary use can lead to a problem that is difficult to understand without knowledge of what is happening behind the scenes. Consider the following example: Integer i1 = 127; Integer i2 = 127; System.out.println(i1 == i2); // Output: true System.out.println(i1 < i2); // Output: false System.out.println(i1 > i2); // Output: false System.out.println(i1+i2); // Output: 254 i1 = 30000; i2 = 30000; System.out.println(i1 == i2); // Output: false System.out.println(i1 < i2); // Output: false System.out.println(i1 > i2); // Output: false i2 = 30001; System.out.println(i1 < i2); // Output: true System.out.println(i1+i2); // Output: 60001 With one exception, this example’s output is as expected. The exception is the i1 == i2 comparison where each of i1 and i2 contains 30000. Instead of returning true, as is the case where each of i1 and i2 contains 127, i1 == i2 returns false. What is causing this problem? Examine the generated code and you will discover that Integer i1 = 127; is converted to Integer i1 = Integer.valueOf(127); and Integer i2 = 127; is converted to Integer i2 = Integer.valueOf(127);. According to valueOf()’s Java documentation, this method takes advantage of caching to improve performance. ■ Note valueOf() is also used when adding a primitive value to a collection. For example, col.add(27) is converted to col.add(Integer.valueOf(27)). Integer maintains an internal cache of unique Integer objects over a small range of values. The low bound of this range is -128, and the high bound defaults to 127. However, you can change the high bound by assigning a different value to system property java.lang.Integer.IntegerCache.high (via the java.lang.System class’s String setProperty(String prop, String value) method—I demonstrated this method’s getProperty() counterpart in Chapter 4). ■ Note Each of Byte, Long, and Short also maintains an internal cache of unique Byte, Long, and Short objects, respectively. 328 CHAPTER 5 COLLECTING OBJECTS Because of the cache, each Integer.valueOf(127) call returns the same Integer object reference, which is why i1 == i2 (which compares references) evaluates to true. Because 30000 lies outside of the default range, each Integer.valueOf(30000) call returns a reference to a new Integer object, which is why i1 == i2 evaluates to false. In contrast to == and !=, which do not unbox the boxed values prior to the comparison, operators such as <, >, and + unbox these values before performing their operations. As a result, i1 < i2 is converted to i1.intValue() < i2.intValue() and i1+i2 is converted to i1.intValue()+i2.intValue(). ■ Caution Don’t assume that autoboxing and unboxing are used in the context of the == and != operators. List A list is an ordered collection, which is also known as a sequence. Elements can be stored in and accessed from specific locations via integer indexes. Some of these elements may be duplicates or null (when the list’s implementation allows null elements). Lists are described by the List interface, whose generic type is List<E>. List extends Collection and redeclares its inherited methods, partly for convenience. It also redeclares iterator(), add(), remove(), equals(), and hashCode() to place extra conditions on their contracts. For example, List’s contract for add() specifies that it appends an element to the end of the list, rather than adding the element to the collection. List also declares Table 5-2’s list-specific methods. Table 5-2. List-specific Methods Method Description void add(int index, E e) Insert element e into this list at position index. Shift the element currently at this position (if any) and any subsequent elements to the right. This method throws UnsupportedOperationException when this list does not support add(), ClassCastException when e’s class is inappropriate for this list, IllegalArgumentException when some property of e prevents it from being added to this list, NullPointerException when e contains the null reference and this list doesn’t support null elements, and java.lang.IndexOutOfBoundsException when index is less than 0 or index is greater than size(). boolean addAll(int index, Collection<? extends E> c) Insert all c’s elements into this list starting at position index and in the order that they are returned by c's iterator. Shift the element currently at this position (if any) and any subsequent elements to the right. This method throws UnsupportedOperationException when this list does not support addAll(), ClassCastException when the class of one of c’s elements is inappropriate for this list, IllegalArgumentException when some property of an 329 CHAPTER 5 COLLECTING OBJECTS This book was purchased by [email protected] element prevents it from being added to this list, NullPointerException when c contains the null reference or when one of its elements is null and this list does not support null elements, and IndexOutOfBoundsException when index is less than 0 or index is greater than size(). 330 E get(int index) Return the element stored in this list at position index. This method throws IndexOutOfBoundsException when index is less than 0 or index is greater than or equal to size(). int indexOf(Object o) Return the index of the first occurrence of element o in this list, or -1 when this list does not contain the element. This method throws ClassCastException when o’s class is inappropriate for this list, and NullPointerException when o contains the null reference and this list does not support null elements. int lastIndexOf(Object o) Return the index of the last occurrence of element o in this list, or -1 when this list does not contain the element. This method throws ClassCastException when o’s class is inappropriate for this list, and NullPointerException when o contains the null reference and this list does not support null elements. ListIterator<E> listIterator() Return a list iterator over the elements in this list. The elements are returned in the same order as they appear in the list. ListIterator<E> listIterator(int index) Return a list iterator over this list’s elements starting with the element at index. Elements are returned in the same order as they appear in the list. IndexOutOfBoundsException is thrown when index is less than 0 or index is greater than size(). E remove(int index) Remove the element at position index from this list, shift any subsequent elements to the left, and return this element. UnsupportedOperationException is thrown when this list does not support remove(); IndexOutOfBoundsException is thrown when index is less than 0, or greater than or equal to size(). E set(int index, E e) Replace the element at position index in this list with element e and return the element previously stored at this position. This method throws UnsupportedOperationException when this list does not support set(), ClassCastException when e’s class is inappropriate for this list, IllegalArgumentException when some property of e prevents it from being added to this list, NullPointerException when e contains the null reference and this list does not support null elements, and CHAPTER 5 COLLECTING OBJECTS IndexOutOfBoundsException when index is less than 0 or index is greater than or equal to size(). List<E> subList(int fromIndex, int toIndex) Return a view (discussed later) of the portion of this list between fromIndex (inclusive) and toIndex (exclusive). (If fromIndex and toIndex are equal, the returned list is empty.) The returned list is backed by this list, so nonstructural changes in the returned list are reflected in this list and viceversa. The returned list supports all the optional list methods (those methods that can throw UnsupportedOperationException) supported by this list. This method throws IndexOutOfBoundsException when fromIndex is less than 0, toIndex is greater than size(), or fromIndex is greater than toIndex. Table 5-2 refers to the ListIterator interface, which is more flexible than its Iterator superinterface in that ListIterator provides methods for iterating over a list in either direction, modifying the list during iteration, and obtaining the iterator’s current position in the list. ■ Note The Iterator and ListIterator instances that are returned by the iterator() and listIterator() methods in the ArrayList and LinkedList List implementation classes are fail-fast: when a list is structurally modified (by calling the implementation’s add() method to add a new element, for example) after the iterator is created, in any way except through the iterator’s own add() or remove() methods, the iterator throws ConcurrentModificationException. Therefore, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, nondeterministic behavior at some time in the future. ListIterator declares the following methods: • void add(E e) inserts e into the list being iterated over. This element is inserted immediately before the next element that would be returned by next(), if any, and after the next element that would be returned by previous(), if any. This method throws UnsupportedOperationException when this list iterator does not support add(), ClassCastException when e’s class is inappropriate for this list, and IllegalArgumentException when some property of e prevents it from being added to this list. • boolean hasNext() returns true when this list iterator has more elements when traversing the list in the forward direction. • boolean hasPrevious() returns true when this list iterator has more elements when traversing the list in the reverse direction. • E next() returns the next element in this list and advances the cursor position. This method throws NoSuchElementException when there is no next element. 331 CHAPTER 5 COLLECTING OBJECTS • int nextIndex() returns the index of the element that would be returned by a subsequent call to next(), or the size of the list when at the end of the list. • E previous() returns the previous element in this list and moves the cursor position backwards. This method throws NoSuchElementException when there is no previous element. • int previousIndex() returns the index of the element that would be returned by a subsequent call to previous(), or -1 when at the beginning of the list. • void remove() removes from the list the last element that was returned by next() or previous(). This call can be made only once per call to next() or previous(). Furthermore, it can be made only when add() has not been called after the last call to next() or previous(). This method throws UnsupportedOperationException when this list iterator does not support remove(), and IllegalStateException when neither next() nor previous() has been called, or remove() or add() has already been called after the last call to next() or previous(). • void set(E e) replaces the last element returned by next() or previous() with element e. This call can be made only when neither remove() nor add() has been called after the last call to next() or previous(). This method throws UnsupportedOperationException when this list iterator does not support set(), ClassCastException when e’s class is inappropriate for this list, IllegalArgumentException when some property of e prevents it from being added to this list, and IllegalStateException when neither next() nor previous() has been called, or remove() or add() has already been called after the last call to next() or previous(). A ListIterator instance does not have the concept of a current element. Instead, it has the concept of a cursor for navigating through a list. The nextIndex() and previousIndex() methods return the cursor position, which always lies between the element that would be returned by a call to previous() and the element that would be returned by a call to next(). A list iterator for a list of length n has n+1 possible cursor positions, as illustrated by each caret (^) as shown here: Element(0) cursor positions: ^ Element(1) ^ Element(2) ^ ... Element(n-1) ^ ^ The remove() and set() methods are not defined in terms of the cursor position; they are defined to operate on the last element returned by a call to next() or previous(). ■ Note You can mix calls to next() and previous() as long as you are careful. Keep in mind that the first call to previous() returns the same element as the last call to next(). Furthermore, the first call to next() following a sequence of calls to previous() returns the same element as the last call to previous(). Table 5-2’s description of the subList() method refers to the concept of a view, which is a list that is backed by another list. Changes that are made to the view are reflected in this backing list. The view can cover the entire list or, as subList()’s name implies, only part of the list. 332 CHAPTER 5 COLLECTING OBJECTS The subList() method is useful for performing range-view operations over a list in a compact manner. For example, list.subList(fromIndex, toIndex).clear(); removes a range of elements from list where the first element is located at fromIndex and the last element is located at toIndex-1. ■ Caution A view’s meaning becomes undefined when changes are made to the backing list. Therefore, you should only use subList() temporarily, whenever you need to perform a sequence of range operations on the backing list. ArrayList The ArrayList class provides a list implementation that is based on an internal array (see Chapters 1 and 2). As a result, access to the list’s elements is fast. However, because elements must be moved to open a space for insertion or to close a space after deletion, insertions and deletions of elements is slow. ArrayList supplies three constructors: • ArrayList() creates an empty array list with an initial capacity (storage space) of ten elements. Once this capacity is reached, a larger array is created, elements from the current array are copied into the larger array, and the larger array becomes the new current array. This process repeats as more elements are added to the array list. • ArrayList(Collection<? extends E> c) creates an array list containing c’s elements in the order in which they are returned by c’s iterator. NullPointerException is thrown when c contains the null reference. • ArrayList(int initialCapacity) creates an empty array list with an initial capacity of initialCapacity elements. IllegalArgumentException is thrown when initialCapacity is negative. Listing 5-1 demonstrates an array list. Listing 5-1. A demonstration of an array-based list import java.util.ArrayList; import java.util.List; class ArrayListDemo { public static void main(String[] args) { List<String> ls = new ArrayList<>(); String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; for (String weekDay: weekDays) ls.add(weekDay); dump("ls:", ls); ls.set(ls.indexOf("Wed"), "Wednesday"); dump("ls:", ls); 333 CHAPTER 5 COLLECTING OBJECTS ls.remove(ls.lastIndexOf("Fri")); dump("ls:", ls); } static void dump(String title, List<String> ls) { System.out.print(title+" "); for (String s: ls) System.out.print(s+" "); System.out.println(); } } The List<String> ls = new ArrayList<>(); assignment reveals a couple of items to note: • I’ve declared variable ls to be of List<String> interface type, and have assigned to this variable a reference to an instance of the ArrayList class that implements this interface. When working with the Collections Framework, it is common practice to declare variables to be of interface type. Doing so eliminates extensive code changes when you need to work with a different implementation class; for example, List<String> ls = new LinkedList<>();. Check out Chapter 2’s “Why Use Interfaces?” section for more information about this practice. • The diamond operator <> (which is new in Java 7) reduces verbosity by forcing the compiler to infer actual type arguments for the constructors of generic classes. Without this operator, I would need to specify String as the actual type argument passed to ArrayList<E>, resulting in the more verbose List<String> ls = new ArrayList<String>(); instead of the shorter List<String> ls = new ArrayList<>();. (I don’t regard the diamond operator as a true operator, which is why I don’t include it in Chapter 1’s table of operators—Table 1-3.) The dump() method’s enhanced for statement uses iterator(), hasNext(), and next() behind the scenes. When you run this application, it generates the following output: ls: Sun Mon Tue Wed Thu Fri Sat ls: Sun Mon Tue Wednesday Thu Fri Sat ls: Sun Mon Tue Wednesday Thu Sat LinkedList The LinkedList class provides a list implementation that is based on linked nodes. Because links must be traversed, access to the list’s elements is slow. However, because only node references need to be changed, insertions and deletions of elements is fast. (I will introduce you to nodes later in this chapter.) LinkedList supplies two constructors: • LinkedList() creates an empty linked list. • LinkedList(Collection<? extends E> c) creates a linked list containing c’s elements in the order in which they are returned by c’s iterator. NullPointerException is thrown when c contains the null reference. Listing 5-2 demonstrates a linked list. 334 CHAPTER 5 COLLECTING OBJECTS Listing 5-2. A demonstration of a linked list of nodes import java.util.LinkedList; import java.util.List; import java.util.ListIterator; class LinkedListDemo { public static void main(String[] args) { List<String> ls = new LinkedList<>(); String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; for (String weekDay: weekDays) ls.add(weekDay); dump("ls:", ls); ls.add(1, "Sunday"); ls.add(3, "Monday"); ls.add(5, "Tuesday"); ls.add(7, "Wednesday"); ls.add(9, "Thursday"); ls.add(11, "Friday"); ls.add(13, "Saturday"); dump("ls:", ls); ListIterator<String> li = ls.listIterator(ls.size()); while (li.hasPrevious()) System.out.print(li.previous()+" "); System.out.println(); } static void dump(String title, List<String> ls) { System.out.print(title+" "); for (String s: ls) System.out.print(s+" "); System.out.println(); } } This application demonstrates that each successive add() method call must increase its index by 2 to account for the previously added element when adding longer weekday names to the list. It also shows you how to output a list in reverse order: return a list iterator with its cursor initialized past the end of the list and repeatedly call previous(). When you run this application, it generates the following output: ls: Sun Mon Tue Wed Thu Fri Sat ls: Sun Sunday Mon Monday Tue Tuesday Wed Wednesday Thu Thursday Fri Friday Sat Saturday Saturday Sat Friday Fri Thursday Thu Wednesday Wed Tuesday Tue Monday Mon Sunday Sun Set A set is a collection that contains no duplicate elements. In other words, a set contains no pair of elements e1 and e2 such that e1.equals(e2) returns true. Furthermore, a set can contain at most one null element. Sets are described by the Set interface, whose generic type is Set<E>. 335 CHAPTER 5 COLLECTING OBJECTS Set extends Collection and redeclares its inherited methods, for convenience and also to add stipulations to the contracts for add(), equals(), and hashCode(), to address how they behave in a set context. Also, Set’s documentation states that all constructors of implementation classes must create sets that contain no duplicate elements. Set does not introduce new methods. TreeSet The TreeSet class provides a set implementation that is based on a tree data structure. As a result, elements are stored in sorted order. However, accessing these elements is somewhat slower than with the other Set implementations (which are not sorted) because links must be traversed. ■ Note Check out Wikipedia’s “Tree (data structure)” entry (http://en.wikipedia.org/wiki/Tree_(data_structure)) to learn about trees. TreeSet supplies four constructors: • TreeSet() creates a new, empty tree set that is sorted according to the natural ordering of its elements. All elements inserted into the set must implement the Comparable interface. • TreeSet(Collection<? extends E> c) creates a new tree set containing c’s elements, sorted according to the natural ordering of its elements. All elements inserted into the new set must implement the Comparable interface. This constructor throws ClassCastException when c’s elements do not implement Comparable or are not mutually comparable, and NullPointerException when c contains the null reference. • TreeSet(Comparator<? super E> comparator) creates a new, empty tree set that is sorted according to the specified comparator. Passing null to comparator implies that natural ordering will be used. • TreeSet(SortedSet<E> s) creates a new tree set containing the same elements and using the same ordering as s. (I discuss sorted sets later in this chapter.) This constructor throws NullPointerException when s contains the null reference. Listing 5-3 demonstrates a tree set. Listing 5-3. A demonstration of a tree set with String elements sorted according to their natural ordering import java.util.Set; import java.util.TreeSet; class TreeSetDemo { public static void main(String[] args) { 336 CHAPTER 5 COLLECTING OBJECTS Set<String> ss = new TreeSet<>(); String[] fruits = {"apples", "pears", "grapes", "bananas", "kiwis"}; for (String fruit: fruits) ss.add(fruit); dump("ss:", ss); } static void dump(String title, Set<String> ss) { System.out.print(title+" "); for (String s: ss) System.out.print(s+" "); System.out.println(); } } Because String implements Comparable, it is legal for this application to use the TreeSet() constructor to insert the contents of the fruits array into the set. When you run this application, it generates the following output: ss: apples bananas grapes kiwis pears HashSet The HashSet class provides a set implementation that is backed by a hashtable data structure (implemented as a HashMap instance, discussed later, which provides a quick way to determine if an element has already been stored in this structure). Although this class provides no ordering guarantees for its elements, HashSet is much faster than TreeSet. Furthermore, HashSet permits the null reference to be stored in its instances. ■ Note Check out Wikipedia’s “Hash table” entry (http://en.wikipedia.org/wiki/Hash_table) to learn about hashtables. HashSet supplies four constructors: • HashSet() creates a new, empty hashset where the backing HashMap instance has an initial capacity of 16 and a load factor of 0.75. You will learn what these items mean when I discuss HashMap later in this chapter. • HashSet(Collection<? extends E> c) creates a new hashset containing c’s elements. The backing HashMap has an initial capacity sufficient to contain c’s elements and a load factor of 0.75. This constructor throws NullPointerException when c contains the null reference. • HashSet(int initialCapacity) creates a new, empty hashset where the backing HashMap instance has the capacity specified by initialCapacity and a load factor of 0.75. This constructor throws IllegalArgumentException when initialCapacity’s value is less than 0. 337 CHAPTER 5 COLLECTING OBJECTS • HashSet(int initialCapacity, float loadFactor) creates a new, empty hashset where the backing HashMap instance has the capacity specified by initialCapacity and the load factor specified by loadFactor. This constructor throws IllegalArgumentException when initialCapacity is less than 0 or when loadFactor is less than or equal to 0. Listing 5-4 demonstrates a hashset. Listing 5-4. A demonstration of a hashset with String elements unordered import java.util.HashSet; import java.util.Set; class HashSetDemo { public static void main(String[] args) { Set<String> ss = new HashSet<>(); String[] fruits = {"apples", "pears", "grapes", "bananas", "kiwis", "pears", null}; for (String fruit: fruits) ss.add(fruit); dump("ss:", ss); } static void dump(String title, Set<String> ss) { System.out.print(title+" "); for (String s: ss) System.out.print(s+" "); System.out.println(); } } In Listing 5-3’s TreeSetDemo application, I did not add null to the fruits array because TreeSet throws NullPointerException when it detects an attempt to add this element. In contrast, HashSet permits null to be added, which is why Listing 5-4 includes null in HashSetDemo’s fruits array. When you run this application, it generates unordered output such as the following: ss: null grapes bananas kiwis pears apples Suppose you want to add instances of your classes to a hashset. As with String, your classes must override equals() and hashCode(); otherwise, duplicate class instances can be stored in the hashset. For example, Listing 5-5 presents the source code to an application whose Planet class overrides equals() but fails to also override hashCode(). Listing 5-5. A custom Planet class not overriding hashCode() import java.util.HashSet; import java.util.Set; class CustomClassAndHashSet { public static void main(String[] args) 338 CHAPTER 5 COLLECTING OBJECTS { Set<Planet> sp = new HashSet<>(); sp.add(new Planet("Mercury")); sp.add(new Planet("Venus")); sp.add(new Planet("Earth")); sp.add(new Planet("Mars")); sp.add(new Planet("Jupiter")); sp.add(new Planet("Saturn")); sp.add(new Planet("Uranus")); sp.add(new Planet("Neptune")); sp.add(new Planet("Fomalhaut b")); Planet p1 = new Planet("51 Pegasi b"); sp.add(p1); Planet p2 = new Planet("51 Pegasi b"); sp.add(p2); System.out.println(p1.equals(p2)); System.out.println(sp); } } class Planet { private String name; Planet(String name) { this.name = name; } @Override public boolean equals(Object o) { if (!(o instanceof Planet)) return false; Planet p = (Planet) o; return p.name.equals(name); } String getName() { return name; } @Override public String toString() { return name; } } Listing 5-5’s Planet class declares a single name field of type String. Although it might seem pointless to declare Planet with a single String field because I could refactor this listing to remove Planet and work with String, I might want to introduce additional fields to Planet (perhaps to store a planet’s mass and other characteristics) in the future. When you run this application, it generates unordered output such as the following: true 339 CHAPTER 5 COLLECTING OBJECTS [Venus, Fomalhaut b, Uranus, Mars, Neptune, Jupiter, Earth, Mercury, Saturn, 51 Pegasi b, 51 Pegasi b] This output reveals two 51 Pegasi b elements in the hashset. Although these elements are equal from the perspective of the overriding equals() method (the first output line, true, proves this point), overriding equals() is not enough to avoid duplicate elements being stored in a hashset: you must also override hashCode(). The easiest way to override hashCode() in Listing 5-5’s Planet class is to have the overriding method call the name field’s hashCode() method and return its value. (This technique only works with a class whose single reference field’s class provides a valid hashCode() method.) Listing 5-6 presents this overriding hashCode() method. Listing 5-6. A custom Planet class overriding hashCode() import java.util.HashSet; import java.util.Set; This book was purchased by [email protected] class CustomClassAndHashSet { public static void main(String[] args) { Set<Planet> sp = new HashSet<>(); sp.add(new Planet("Mercury")); sp.add(new Planet("Venus")); sp.add(new Planet("Earth")); sp.add(new Planet("Mars")); sp.add(new Planet("Jupiter")); sp.add(new Planet("Saturn")); sp.add(new Planet("Uranus")); sp.add(new Planet("Neptune")); sp.add(new Planet("Fomalhaut b")); Planet p1 = new Planet("51 Pegasi b"); sp.add(p1); Planet p2 = new Planet("51 Pegasi b"); sp.add(p2); System.out.println(p1.equals(p2)); System.out.println(sp); } } class Planet { private String name; Planet(String name) { this.name = name; } @Override public boolean equals(Object o) { if (!(o instanceof Planet)) return false; Planet p = (Planet) o; 340 CHAPTER 5 COLLECTING OBJECTS return p.name.equals(name); } String getName() { return name; } @Override public int hashCode() { return name.hashCode(); } @Override public String toString() { return name; } } Compile Listing 5-6 (javac CustomClassAndHashSet.java) and run the application (java CustomClassAndHashSet). You will observe output (similar to that shown below) that reveals no duplicate elements: true [Saturn, Earth, Uranus, Fomalhaut b, 51 Pegasi b, Venus, Jupiter, Mercury, Mars, Neptune] ■ Note LinkedHashSet is a subclass of HashSet that uses a linked list to store its elements. As a result, LinkedHashSet’s iterator returns elements in the order in which they were inserted. For example, if Listing 5-4 had specified Set<String> ss = new LinkedHashSet<>();, the application’s output would have been ss: apples pears grapes bananas kiwis null. Also, LinkedHashSet offers slower performance than HashSet and faster performance than TreeSet. EnumSet Chapter 3 introduced you to traditional enumerated types and their enum replacement. (An enum is an enumerated type that is expressed via reserved word enum.) The following example demonstrates the traditional enumerated type: static static static static static static static final final final final final final final int int int int int int int SUNDAY = 1; MONDAY = 2; TUESDAY = 4; WEDNESDAY = 8; THURSDAY = 16; FRIDAY = 32; SATURDAY = 64; 341 CHAPTER 5 COLLECTING OBJECTS Although the enum has many advantages over the traditional enumerated type, the traditional enumerated type is less awkward to use when combining constants into a set; for example, static final int DAYS_OFF = SUNDAY | MONDAY;. DAYS_OFF is an example of an integer-based, fixed-length bitset, which is a set of bits where each bit indicates that its associated member belongs to the set when the bit is set to 1, and is absent from the set when the bit is set to 0. ■ Note An int-based bitset cannot contain more than 32 members because int has a size of 32 bits. Similarly, a long-based bitset cannot contain more than 64 members because long has a size of 64 bits. This bitset is formed by bitwise inclusive ORing the traditional enumerated type’s integer constants together via the bitwise inclusive OR operator (|): you could also use +. Each constant must be a unique power of two (starting with one) because otherwise it is impossible to distinguish between the members of this bitset. To determine if a constant belongs to the bitset, create an expression that involves the bitwise AND operator (&). For example, ((DAYS_OFF&MONDAY) == MONDAY) bitwise ANDs DAYS_OFF (3) with MONDAY (2), which results in 2. This value is compared via == with MONDAY (2), and the result of the expression is true: MONDAY is a member of the DAYS_OFF bitset. You can accomplish the same task with an enum by instantiating an appropriate Set implementation class and calling the add() method multiple times to store the constants in the set. Listing 5-7 illustrates this more awkward alternative. Listing 5-7. Creating the Set equivalent of DAYS_OFF import java.util.Set; import java.util.TreeSet; enum Weekday { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } class DaysOff { public static void main(String[] args) { Set<Weekday> daysOff = new TreeSet<>(); daysOff.add(Weekday.SUNDAY); daysOff.add(Weekday.MONDAY); System.out.println(daysOff); } } When you run this application, it generates the following output: [SUNDAY, MONDAY] 342 CHAPTER 5 COLLECTING OBJECTS ■ Note The constants’ ordinals and not their names are stored in the tree set, which is why the names appear unordered even though the constants are stored in sorted order of their ordinals. As well as being more awkward to use (and verbose) than the bitset, the Set alternative requires more memory to store each constant and is not as fast. Because of these problems, EnumSet was introduced. The EnumSet class provides a Set implementation that is based on a bitset. Its elements are constants that must come from the same enum, which is specified when the enum set is created. Null elements are not permitted; any attempt to store a null element results in a thrown NullPointerException. Listing 5-8 demonstrates EnumSet. Listing 5-8. Creating the EnumSet equivalent of DAYS_OFF import java.util.EnumSet; import java.util.Iterator; import java.util.Set; enum Weekday { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } class EnumSetDemo { public static void main(String[] args) { Set<Weekday> daysOff = EnumSet.of(Weekday.SUNDAY, Weekday.MONDAY); Iterator<Weekday> iter = daysOff.iterator(); while (iter.hasNext()) System.out.println(iter.next()); } } EnumSet, whose generic type is EnumSet<E extends Enum<E>>, provides various class methods for conveniently constructing enum sets. For example, <E extends Enum<E>> EnumSet<E> of(E e1, E e2) returns an EnumSet instance consisting of elements e1 and e2. In this example, those elements are Weekday.SUNDAY and Weekday.MONDAY. When you run this application, it generates the following output: SUNDAY MONDAY ■ Note As well as providing several overloaded of() methods, EnumSet provides other methods for conveniently creating enum sets. For example, allOf() returns an EnumSet instance that contains all of an enum’s constants, where this method’s solitary argument is a class literal that identifies the enum: 343 CHAPTER 5 COLLECTING OBJECTS Set<Weekday> allWeekDays = EnumSet.allOf(Weekday.class); Similarly, range() returns an EnumSet instance containing a range of an enum’s elements (with the range’s limits as specified by this method’s two arguments): for (WeekDay wd : EnumSet.range(WeekDay.MONDAY, WeekDay.FRIDAY)) System.out.println(wd); SortedSet TreeSet is an example of a sorted set, which is a set that maintains its elements in ascending order, sorted according to their natural ordering or according to a comparator that is supplied when the sorted set is created. Sorted sets are described by the SortedSet interface. SortedSet, whose generic type is SortedSet<E>, extends Set. With two exceptions, the methods it inherits from Set behave identically on sorted sets as on other sets: • The Iterator instance returned from iterator() traverses the sorted set in ascending element order. • The array returned by toArray() contains the sorted set’s elements in order. ■ Note Although not guaranteed, the toString() methods of SortedSet implementations in the Collections Framework (such as TreeSet) return a string containing all the sorted set’s elements in order. SortedSet’s documentation requires that an implementation must provide the four standard constructors that I presented in my discussion of TreeSet. Furthermore, implementations of this interface must implement the methods that are described in Table 5-3. Table 5-3. SortedSet-specific Methods 344 Method Description Comparator<? super E> comparator() Return the comparator used to order the elements in this set, or null when this set uses the natural ordering of its elements. E first() Return the first (lowest) element currently in this set, or throw a NoSuchElementException instance when this set is empty. CHAPTER 5 COLLECTING OBJECTS SortedSet<E> headSet(E toElement) Return a view of that portion of this set whose elements are strictly less than toElement. The returned set is backed by this set, so changes in the returned set are reflected in this set and vice versa. The returned set supports all optional set operations that this set supports. This method throws ClassCastException when toElement is not compatible with this set’s comparator (or, when the set has no comparator, when toElement does not implement Comparable), NullPointerException when toElement is null and this set does not permit null elements, and IllegalArgumentException when this set has a restricted range and toElement lies outside of this range’s bounds. E last() Return the last (highest) element currently in this set, or throw a NoSuchElementException instance when this set is empty. SortedSet<E> subSet(E fromElement, E toElement) Return a view of the portion of this set whose elements range from fromElement, inclusive, to toElement, exclusive. (When fromElement and toElement are equal, the returned set is empty.) The returned set is backed by this set, so changes in the returned set are reflected in this set and vice versa. The returned set supports all optional set operations that this set supports. This method throws ClassCastException when fromElement and toElement cannot be compared to one another using this set’s comparator (or, when the set has no comparator, using natural ordering), NullPointerException when fromElement or toElement is null and this set does not permit null elements, and IllegalArgumentException when fromElement is greater than toElement or when this set has a restricted range and fromElement or toElement lies outside of this range’s bounds. SortedSet<E> tailSet(E fromElement) Return a view of that portion of this set whose elements are greater than or equal to fromElement. The returned set is backed by this set, so changes in the returned set are reflected in this set and vice versa. The returned set supports all optional set operations that this set supports. This method throws ClassCastException when fromElement is not compatible with this set’s comparator (or, when the set has no comparator, when fromElement does not implement Comparable), NullPointerException when fromElement is null and this set does not permit null elements, and IllegalArgumentException when this set has a restricted range and fromElement lies outside of the range’s bounds. The set-based range views returned from headSet(), subSet(), and tailSet() are analogous to the list-based range view returned from List’s subList() method except that a set-based range view remains 345 CHAPTER 5 COLLECTING OBJECTS valid even when the backing sorted set is modified. As a result, a set-based range view can be used for a lengthy period of time. ■ Note Unlike a list-based range view whose endpoints are elements in the backing list, the endpoints of a setbased range view are absolute points in element space, allowing a set-based range view to serve as a window onto a portion of the set’s element space. Any changes made to the set-based range view are written back to the backing sorted set and vice versa. Each range view returned by headSet(), subSet(), or tailSet() is half open because it does not include its high endpoint (headSet() and subSet()) or its low endpoint (tailSet()). For the first two methods, the high endpoint is specified by argument toElement; for the last method, the low endpoint is specified by argument fromElement. ■ Note You could also regard the returned range view as being half closed because it includes only one of its endpoints. Listing 5-9 demonstrates a sorted set based on a tree set. Listing 5-9. A sorted set of fruit and vegetable names import java.util.SortedSet; import java.util.TreeSet; class SortedSetDemo { public static void main(String[] args) { SortedSet<String> sss = new TreeSet<>(); String[] fruitAndVeg = { "apple", "potato", "turnip", "banana", "corn", "carrot", "cherry", "pear", "mango", "strawberry", "cucumber", "grape", "banana", "kiwi", "radish", "blueberry", "tomato", "onion", "raspberry", "lemon", "pepper", "squash", "melon", "zucchini", "peach", "plum", "turnip", "onion", "nectarine" }; System.out.println("Array size = "+fruitAndVeg.length); for (String fruitVeg: fruitAndVeg) sss.add(fruitVeg); dump("sss:", sss); System.out.println("Sorted set size = "+sss.size()); System.out.println("First element = "+sss.first()); 346 CHAPTER 5 COLLECTING OBJECTS System.out.println("Last element = "+sss.last()); System.out.println("Comparator = "+sss.comparator()); dump("hs:", sss.headSet("n")); dump("ts:", sss.tailSet("n")); System.out.println("Count of p-named fruits & vegetables = "+ sss.subSet("p", "q").size()); System.out.println("Incorrect count of c-named fruits & vegetables = "+ sss.subSet("carrot", "cucumber").size()); System.out.println("Correct count of c-named fruits & vegetables = "+ sss.subSet("carrot", "cucumber\0").size()); } static void dump(String title, SortedSet<String> sss) { System.out.print(title+" "); for (String s: sss) System.out.print(s+" "); System.out.println(); } } When you run this application, it generates the following output: Array size = 29 sss: apple banana blueberry carrot cherry corn cucumber grape kiwi lemon mango melon nectarine onion peach pear pepper plum potato radish raspberry squash strawberry tomato turnip zucchini Sorted set size = 26 First element = apple Last element = zucchini Comparator = null hs: apple banana blueberry carrot cherry corn cucumber grape kiwi lemon mango melon ts: nectarine onion peach pear pepper plum potato radish raspberry squash strawberry tomato turnip zucchini Count of p-named fruits & vegetables = 5 Incorrect count of c-named fruits & vegetables = 3 Correct count of c-named fruits & vegetables = 4 This output reveals that the sorted set’s size is less than the array’s size because a set cannot contain duplicate elements: the duplicate banana, turnip, and onion elements are not stored in the sorted set. The comparator() method returns null because the sorted set was not created with a comparator. Instead, the sorted set relies on the natural ordering of String elements to store them in sorted order. The headSet() and tailSet() methods are called with argument "n" to return, respectively, a set of elements whose names begin with a letter that is strictly less than n, and a letter that is greater than or equal to n. Finally, the output shows you that you must be careful when passing an upper limit to subSet(). As you can see, ss.subSet("carrot", "cucumber") does not include cucumber in the returned range view because cucumber is subSet()’s high endpoint. To include cucumber in the range view, you need to form a closed range or closed interval (both endpoints are included). With String objects, you accomplish this task by appending \0 to the string. For example, ss.subSet("carrot", "cucumber\0") includes cucumber because it is less than cucumber\0. 347 CHAPTER 5 COLLECTING OBJECTS This same technique can be applied wherever you need to form an open range or open interval (neither endpoint is included). For example, ss.subSet("carrot\0", "cucumber") does not include carrot because it is less than carrot\0. Furthermore, it does not include high endpoint cucumber. ■ Note When you want to create closed and open ranges for elements created from your own classes, you need to provide some form of predecessor() and successor() methods that return an element’s predecessor and successor. You need to be careful when designing classes that work with sorted sets. For example, the class must implement Comparable when you plan to store the class’s instances in a sorted set where these elements are sorted according to their natural ordering. Consider Listing 5-10. Listing 5-10. A custom Employee class not implementing Comparable import java.util.SortedSet; import java.util.TreeSet; class CustomClassAndSortedSet { public static void main(String[] args) { SortedSet<Employee> sse = new TreeSet<>(); sse.add(new Employee("Sally Doe")); sse.add(new Employee("Bob Doe")); // ClassCastException thrown here sse.add(new Employee("John Doe")); System.out.println(sse); } } class Employee { private String name; Employee(String name) { this.name = name; } @Override public String toString() { return name; } } When you run this application, it generates the following output: Exception in thread "main" java.lang.ClassCastException: Employee cannot be cast to java.lang.Comparable at java.util.TreeMap.compare(TreeMap.java:1188) 348 CHAPTER 5 COLLECTING OBJECTS at java.util.TreeMap.put(TreeMap.java:531) at java.util.TreeSet.add(TreeSet.java:255) at CustomClassAndSortedSet.main(CustomClassAndSortedSet.java:9) The ClassCastException instance is thrown during the second add() method call because the sorted set implementation, an instance of TreeSet, is unable to call the second Employee element’s compareTo() method, because Employee does not implement Comparable. The solution to this problem is to have the class implement Comparable, which is exactly what is revealed in Listing 5-11. Listing 5-11. Making Employee elements comparable import java.util.SortedSet; import java.util.TreeSet; class CustomClassAndSortedSet { public static void main(String[] args) { SortedSet<Employee> sse = new TreeSet<>(); sse.add(new Employee("Sally Doe")); sse.add(new Employee("Bob Doe")); Employee e1 = new Employee("John Doe"); Employee e2 = new Employee("John Doe"); sse.add(e1); sse.add(e2); System.out.println(sse); System.out.println(e1.equals(e2)); } } class Employee implements Comparable<Employee> { private String name; Employee(String name) { this.name = name; } @Override public int compareTo(Employee e) { return name.compareTo(e.name); } @Override public String toString() { return name; } } Listing 5-11’s main() method differs from Listing 5-10 in that it also creates two Employee objects initialized to "John Doe", adds these objects to the sorted set, and compares these objects for equality via 349 CHAPTER 5 COLLECTING OBJECTS equals(). Furthermore, Listing 5-11 declares Employee to implement Comparable, introducing a compareTo() method into Employee. When you run this application, it generates the following output: [Bob Doe, John Doe, Sally Doe] false This book was purchased by [email protected] This output shows that only one "John Doe" Employee object is stored in the sorted set. After all, a set cannot contain duplicate elements. However, the false value (resulting from the equals() comparison) also shows that the sorted set’s natural ordering is inconsistent with equals(), which violates SortedSet’s contract: The ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals() if the sorted set is to correctly implement the Set interface. This is so because the Set interface is defined in terms of the equals() operation, but a sorted set performs all element comparisons using its compareTo() (or compare()) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal. Because the application works correctly, why should SortedSet’s contract matter? Although the contract does not appear to matter with respect to the TreeSet implementation of SortedSet, perhaps it will matter in the context of a third-party class that implements this interface. Listing 5-12 shows you how to correct this problem and make Employee instances work with any implementation of a sorted set. Listing 5-12. A contract-compliant Employee class import java.util.SortedSet; import java.util.TreeSet; class CustomClassAndSortedSet { public static void main(String[] args) { SortedSet<Employee> sse = new TreeSet<>(); sse.add(new Employee("Sally Doe")); sse.add(new Employee("Bob Doe")); Employee e1 = new Employee("John Doe"); Employee e2 = new Employee("John Doe"); sse.add(e1); sse.add(e2); System.out.println(sse); System.out.println(e1.equals(e2)); } } class Employee implements Comparable<Employee> { private String name; Employee(String name) { this.name = name; } @Override public int compareTo(Employee e) { 350 CHAPTER 5 COLLECTING OBJECTS return name.compareTo(e.name); } @Override public boolean equals(Object o) { if (!(o instanceof Employee)) return false; Employee e = (Employee) o; return e.name.equals(name); } @Override public String toString() { return name; } } Listing 5-12 corrects the SortedSet contract violation by overriding equals(). Run the resulting application and you will observe [Bob Doe, John Doe, Sally Doe] as the first line of output and true as the second line: the sorted set’s natural ordering is now consistent with equals(). ■ Note Although it is important to override hashCode() whenever you override equals(), I did not override hashCode() (although I overrode equals()) in Listing 5-12’s Employee class to emphasize that tree-based sorted sets ignore hashCode(). NavigableSet TreeSet is an example of a navigable set, which is a sorted set that can be iterated over in descending order as well as ascending order, and which can report closest matches for given search targets. Navigable sets are described by the NavigableSet interface, whose generic type is NavigableSet<E>, which extends SortedSet, and which is described in Table 5-4. Table 5-4. NavigableSet-specific Methods Method Description E ceiling(E e) Return the least element in this set greater than or equal to e, or null when there is no such element. This method throws ClassCastException when e cannot be compared with the elements currently in the set, and NullPointerException when e is null and this set does not permit null elements. Iterator<E> descendingIterator() Return an iterator over the elements in this set, in descending order. Equivalent in effect to descendingSet().iterator(). 351 CHAPTER 5 COLLECTING OBJECTS 352 NavigableSet<E> descendingSet() Return a reverse order view of the elements contained in this set. The descending set is backed by this set, so changes to the set are reflected in the descending set and vice versa. If either set is modified (except through the iterator’s own remove() operation) while iterating over the set, the results of the iteration are undefined. E floor(E e) Return the greatest element in this set less than or equal to e, or null when there is no such element. This method throws ClassCastException when e cannot be compared with the elements currently in the set, and NullPointerException when e is null and this set does not permit null elements. NavigableSet<E> headSet(E toElement, boolean inclusive) Return a view of the portion of this set whose elements are less than (or equal to, when inclusive is true) toElement. The returned set is backed by this set, so changes in the returned set are reflected in this set and vice versa. The returned set supports all optional set operations that this set supports. This method throws ClassCastException when toElement is not compatible with this set’s comparator (or, when the set has no comparator, when toElement does not implement Comparable), NullPointerException when toElement is null and this set does not permit null elements, and IllegalArgumentException when this set has a restricted range and toElement lies outside of this range’s bounds. E higher(E e) Return the least element in this set strictly greater than the given element, or null when there is no such element. This method throws ClassCastException when e cannot be compared with the elements currently in the set, and NullPointerException when e is null and this set does not permit null elements. E lower(E e) Return the greatest element in this set strictly less than the given element, or null when there is no such element. This method throws ClassCastException when e cannot be compared with the elements currently in the set, and NullPointerException when e is null and this set does not permit null elements. E pollFirst() Return and remove the first (lowest) element from this set, or return null when this set is empty. E pollLast() Return and remove the last (highest) element from this set, or return null when this set is empty. NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, Return a view of the portion of this set whose elements range from fromElement to toElement. (When fromElement and toElement are equal, the returned set is empty unless CHAPTER 5 COLLECTING OBJECTS boolean toInclusive) fromInclusive and toInclusive are both true.) The returned set is backed by this set, so changes in the returned set are reflected in this set and vice versa. The returned set supports all optional set operations that this set supports. This method throws ClassCastException when fromElement and toElement cannot be compared to one another using this set’s comparator (or, when the set has no comparator, using natural ordering), NullPointerException when fromElement or toElement is null and this set does not permit null elements, and IllegalArgumentException when fromElement is greater than toElement or when this set has a restricted range and fromElement or toElement lies outside of this range’s bounds. NavigableSet<E> tailSet(E fromElement, boolean inclusive) Return a view of the portion of this set whose elements are greater than (or equal to, when inclusive is true) fromElement. The returned set is backed by this set, so changes in the returned set are reflected in this set and vice versa. The returned set supports all optional set operations that this set supports. This method throws ClassCastException when fromElement is not compatible with this set’s comparator (or, when the set has no comparator, when fromElement does not implement Comparable), NullPointerException when fromElement is null and this set does not permit null elements, and IllegalArgumentException when this set has a restricted range and fromElement lies outside of this range’s bounds. Listing 5-13 demonstrates a navigable set based on a tree set. Listing 5-13. Navigating a set of integers import java.util.Iterator; import java.util.NavigableSet; import java.util.TreeSet; class NavigableSetDemo { public static void main(String[] args) { NavigableSet<Integer> ns = new TreeSet<>(); int[] ints = { 82, -13, 4, 0, 11, -6, 9 }; for (int i: ints) ns.add(i); System.out.print("Ascending order: "); Iterator iter = ns.iterator(); while (iter.hasNext()) System.out.print(iter.next()+" "); System.out.println(); System.out.print("Descending order: "); 353 CHAPTER 5 COLLECTING OBJECTS iter = ns.descendingIterator(); while (iter.hasNext()) System.out.print(iter.next()+" "); System.out.println("\n"); outputClosestMatches(ns, 4); outputClosestMatches(ns.descendingSet(), 12); } static void outputClosestMatches(NavigableSet<Integer> ns, int i) { System.out.println("Element < "+i+" is "+ns.lower(i)); System.out.println("Element <= "+i+" is "+ns.floor(i)); System.out.println("Element > "+i+" is "+ns.higher(i)); System.out.println("Element >= "+i+" is "+ns.ceiling(i)); System.out.println(); } } Listing 5-13 creates a navigable set of Integer elements. It takes advantage of autoboxing to ensure that ints are converted to Integers. When you run this application, it generates the following output: Ascending order: -13 -6 0 4 9 11 82 Descending order: 82 11 9 4 0 -6 -13 Element Element Element Element < 4 is 0 <= 4 is 4 > 4 is 9 >= 4 is 4 Element Element Element Element < 12 is 82 <= 12 is 82 > 12 is 11 >= 12 is 11 The first four output lines beginning with Element pertain to an ascending-order set where the element being matched (4) is a member of the set. The second four Element-prefixed lines pertain to a descending-order set where the element being matched (12) is not a member. As well as letting you conveniently locate set elements via its closest-match methods (ceiling(), floor(), higher(), and lower()), NavigableSet lets you return set views containing all elements within certain ranges, as demonstrated by the following examples: • ns.subSet(-13, true, 9, true): Return all elements from -13 through 9. • ns.tailSet(-6, false): Return all elements greater than -6. • ns.headSet(4, true): Return all elements less than or equal to 4. Finally, you can return and remove from the set the first (lowest) element by calling pollFirst() and the last (highest) element by calling pollLast(). For example, ns.pollFirst() removes and returns -13, and ns.pollLast() removes and returns -82. 354 CHAPTER 5 COLLECTING OBJECTS Queue A queue is a collection in which elements are stored and retrieved in a specific order. Most queues are categorized as one of the following: • First-in, first-out (FIFO) queue: Elements are inserted at the queue’s tail and removed at the queue’s head. • Last-in, first-out (LIFO) queue: Elements are inserted and removed at one end of the queue such that the last element inserted is the first element retrieved. This kind of queue behaves as a stack. • Priority queue: Elements are inserted according to their natural ordering, or according to a comparator that is supplied to the queue implementation. Queue, whose generic type is Queue<E>, extends Collection, redeclaring add() to adjust its contract (insert the specified element into this queue if it is possible to do so immediately without violating capacity restrictions), and inheriting the other methods from Collection. Table 5-5 describes add() and the other Queue-specific methods. Table 5-5. Queue-specific Methods Method Description boolean add(E e) Insert element e into this queue if it is possible to do so immediately without violating capacity restrictions. Return true on success; otherwise, throw IllegalStateException when the element cannot be added at this time because no space is currently available. This method also throws ClassCastException when e’s class prevents e from being added to this queue, NullPointerException when e contains the null reference and this queue does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this queue. E element() Return but do not also remove the element at the head of this queue. This method throws NoSuchElementException when this queue is empty. boolean offer(E e) Insert element e into this queue if it is possible to do so immediately without violating capacity restrictions. Return true on success; otherwise, return false when the element cannot be added at this time because no space is currently available. This method throws ClassCastException when e’s class prevents e from being added to this queue, NullPointerException when e contains the null reference and this queue does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this queue. 355 CHAPTER 5 COLLECTING OBJECTS E peek() Return but do not also remove the element at the head of this queue. This method returns null when this queue is empty. E poll() Return and also remove the element at the head of this queue. This method returns null when this queue is empty. E remove() Return and also remove the element at the head of this queue. This method throws NoSuchElementException when this queue is empty. This is the only difference between remove() and poll(). Table 5-5 reveals two sets of methods: in one set, a method (such as add()) throws an exception when an operation fails; in the other set, a method (such as offer()) returns a special value (false or null) in the presence of failure. The methods that return a special value are useful in the context of capacityrestricted Queue implementations where failure is a normal occurrence. ■ Note The offer() method is generally preferable to add() when using a capacity-restricted queue because offer() does not throw IllegalStateException. Java supplies many Queue implementation classes, where most of these classes are members of the java.util.concurrent package: LinkedBlockingQueue, LinkedTransferQueue, and SynchronousQueue are examples. In contrast, the java.util package provides LinkedList and PriorityQueue as its Queue implementation classes. ■ Caution Many Queue implementation classes do not allow null elements to be added. However, some classes (such as LinkedList) permit null elements. You should avoid adding a null element because null is used as a special return value by the peek() and poll() methods to indicate that a queue is empty. PriorityQueue The PriorityQueue class provides an implementation of a priority queue, which is a queue that orders its elements according to their natural ordering or by a comparator provided when the queue is instantiated. Priority queues do not permit null elements, and do not permit insertion of non-Comparable objects when relying on natural ordering. The element at the head of the priority queue is the least element with respect to the specified ordering. If multiple elements are tied for least element, one of those elements is arbitrarily chosen as the least element. Similarly, the element at the tail of the priority queue is the greatest element, which is arbitrarily chosen when there is a tie. 356 CHAPTER 5 COLLECTING OBJECTS Priority queues are unbounded, but have a capacity that governs the size of the internal array that is used to store the priority queue’s elements. The capacity value is at least as large as the queue’s length, and grows automatically as elements are added to the priority queue. PriorityQueue (whose generic type is PriorityQueue<E>) supplies six constructors: • PriorityQueue() creates a PriorityQueue instance with an initial capacity of 11 elements, and which orders its elements according to their natural ordering. • PriorityQueue(Collection<? extends E> c) creates a PriorityQueue instance containing c’s elements. If c is a SortedSet or PriorityQueue instance, this priority queue will be ordered according to the same ordering. Otherwise, this priority queue will be ordered according to the natural ordering of its elements. This constructor throws ClassCastException when c’s elements cannot be compared to one another according to the priority queue’s ordering, and NullPointerException when c or any of its elements contain the null reference. • PriorityQueue(int initialCapacity) creates a PriorityQueue instance with the specified initialCapacity, and which orders its elements according to their natural ordering. This constructor throws IllegalArgumentException when initialCapacity is less than 1. • PriorityQueue(int initialCapacity, Comparator<? super E> comparator) creates a PriorityQueue instance with the specified initialCapacity, and which orders its elements according to the specified comparator. Natural ordering is used when comparator contains the null reference. This constructor throws IllegalArgumentException when initialCapacity is less than 1. • PriorityQueue(PriorityQueue<? extends E> pq) creates a PriorityQueue instance containing pq’s elements. This priority queue will be ordered according to the same ordering as pq. This constructor throws ClassCastException when pq’s elements cannot be compared to one another according to pq’s ordering, and NullPointerException when pq or any of its elements contains the null reference. • PriorityQueue(SortedSet<? extends E> ss) creates a PriorityQueue instance containing ss’s elements. This priority queue will be ordered according to the same ordering as ss. This constructor throws ClassCastException when sortedSet’s elements cannot be compared to one another according to ss’s ordering, and NullPointerException when sortedSet or any of its elements contains the null reference. Listing 5-14 demonstrates a priority queue. Listing 5-14. Adding randomly generated integers to a priority queue import java.util.PriorityQueue; import java.util.Queue; class PriorityQueueDemo { public static void main(String[] args) { Queue<Integer> qi = new PriorityQueue<>(); for (int i = 0; i < 15; i++) 357 CHAPTER 5 COLLECTING OBJECTS qi.add((int) (Math.random()*100)); while (!qi.isEmpty()) System.out.print(qi.poll()+" "); System.out.println(); } } After creating a priority queue, the main thread adds 15 randomly generated integers (ranging from 0 through 99) to this queue. It then enters a while loop that repeatedly polls the priority queue for the next element and outputs that element until the queue is empty. When you run this application, it outputs a line of 15 integers in ascending numerical order from left to right. For example, I observed the following output from one run: 11 21 29 35 40 53 66 70 72 75 80 83 87 88 89 Because poll() returns null when there are no more elements, I could have coded this loop as follows: Integer i; while ((i = qi.poll()) != null) System.out.print(i+" "); Suppose you want to reverse the order of the previous application’s output so that the largest element appears on the left and the smallest element appears on the right. As Listing 5-15 demonstrates, you can achieve this task by passing a comparator to the appropriate PriorityQueue constructor. Listing 5-15. Using a comparator with a priority queue import java.util.Comparator; import java.util.PriorityQueue; import java.util.Queue; class PriorityQueueDemo { final static int NELEM = 15; public static void main(String[] args) { Comparator<Integer> cmp; cmp = new Comparator<Integer>() { public int compare(Integer e1, Integer e2) { return e2-e1; } }; Queue<Integer> qi = new PriorityQueue<>(NELEM, cmp); for (int i = 0; i < NELEM; i++) qi.add((int) (Math.random()*100)); while (!qi.isEmpty()) System.out.print(qi.poll()+" "); System.out.println(); } } 358 CHAPTER 5 COLLECTING OBJECTS Listing 5-15 is similar to Listing 5-14, but there are some differences. First, I have declared an NELEM constant so that I can easily change both the priority queue’s initial capacity and the number of elements inserted into the priority queue by specifying the new value in one place. Second, Listing 5-15 declares and instantiates an anonymous class that implements Comparator. Its compareTo() method subtracts element e2 from element e1 to achieve descending numerical order. The compiler handles the task of unboxing e2 and e1 by converting e2-e1 to e2.intValue()-e1.intValue(). Finally, Listing 5-15 passes an initial capacity of NELEM elements and the instantiated comparator to the PriorityQueue(int initialCapacity, Comparator<? super E> comparator) constructor. The priority queue will use this comparator to order these elements. Run this application and you will now see a single output line of 15 integers shown in descending numerical order from left to right. For example, I observed this output line: 90 86 78 74 65 53 45 44 30 28 18 9 9 7 5 Deque A deque (pronounced deck) is a double-ended queue in which element insertion or removal occurs at its head or tail. Deques can be used as queues or stacks. Deque, whose generic type is Deque<E>, extends Queue, in which the inherited add(E e) method inserts e at the deque’s tail. Table 5-6 describes Deque-specific methods. Table 5-6. Deque-specific Methods Method Description void addFirst(E e) Insert e at the head of this deque if it is possible to do so immediately without violating capacity restrictions. When using a capacity-restricted deque, it is generally preferable to use method offerFirst(). This method throws IllegalStateException when e cannot be added at this time because of capacity restrictions, ClassCastException when e’s class prevents e from being added to this deque, NullPointerException when e contains the null reference and this deque does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this deque. void addLast(E e) Insert e at the tail of this deque if it is possible to do so immediately without violating capacity restrictions. When using a capacity-restricted deque, it is generally preferable to use method offerLast(). This method throws IllegalStateException when e cannot be added at this time because of capacity restrictions, ClassCastException when e’s class prevents e from being added to this deque, NullPointerException when e contains the null reference and this deque does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this deque. Iterator<E> Return an iterator over the elements in this deque in reverse 359 This book was purchased by [email protected] CHAPTER 5 COLLECTING OBJECTS 360 descendingIterator() sequential order. The elements will be returned in order from last (tail) to first (head). The inherited Iterator<E> iterator() method returns elements from the head to the tail. E element() Retrieve but do not remove the first element of this deque (at the head). This method differs from peek() only in that it throws NoSuchElementException when this deque is empty. This method is equivalent to getFirst(). E getFirst() Retrieve but do not remove the first element of this deque. This method differs from peekFirst() only in that it throws NoSuchElementException when this deque is empty. E getLast() Retrieve but do not remove the last element of this deque. This method differs from peekLast() only in that it throws NoSuchElementException when this deque is empty. boolean offer(E e) Insert e at the tail of this deque if it is possible to do so immediately without violating capacity restrictions, returning true upon success and false when no space is currently available. When using a capacity-restricted deque, this method is generally preferable to the add() method, which can fail to insert an element only by throwing an exception. This method throws ClassCastException when e’s class prevents e from being added to this deque, NullPointerException when e contains the null reference and this deque does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this deque. This method is equivalent to offerLast(). boolean offerFirst(E e) Insert the specified element at the head of this deque unless it would violate capacity restrictions. When using a capacityrestricted deque, this method is generally preferable to the addFirst() method, which can fail to insert an element only by throwing an exception. This method throws ClassCastException when e’s class prevents e from being added to this deque, NullPointerException when e contains the null reference and this deque does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this deque. boolean offerLast(E e) Insert e at the tail of this deque unless it would violate capacity restrictions. When using a capacity-restricted deque, this method is generally preferable to the addLast() method, which can fail to insert an element only by throwing an exception. This method throws ClassCastException when CHAPTER 5 COLLECTING OBJECTS e’s class prevents e from being added to this deque, NullPointerException when e contains the null reference and this deque does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this deque. E peek() Retrieve but do not remove the first element of this deque (at the head), or return null when this deque is empty. This method is equivalent to peekFirst(). E peekFirst() Retrieve but do not remove the first element of this deque (at the head), or return null when this deque is empty. E peekLast() Retrieve but do not remove the last element of this deque (at the tail), or return null when this deque is empty. E poll() Retrieve and remove the first element of this deque (at the head), or return null when this deque is empty. This method is equivalent to pollFirst(). E pollFirst() Retrieve and remove the first element of this deque (at the head), or return null when this deque is empty. E pollLast() Retrieve and remove the last element of this deque (at the tail), or return null when this deque is empty. E pop() Pop an element from the stack represented by this deque. In other words, remove and return the first element of this deque. This method is equivalent to removeFirst(). void push(E e) Push e onto the stack represented by this deque (in other words, at the head of this deque) if it is possible to do so immediately without violating capacity restrictions, returning true upon success and throwing IllegalStateException when no space is currently available. This method also throws ClassCastException when e’s class prevents e from being added to this deque, NullPointerException when e contains the null reference and this deque does not permit null elements to be added, and IllegalArgumentException when some property of e prevents it from being added to this deque. This method is equivalent to addFirst(). E remove() Retrieve and remove the first element of this deque (at the head). This method differs from poll() only in that it throws NoSuchElementException when this deque is empty. This method is equivalent to removeFirst(). 361 CHAPTER 5 COLLECTING OBJECTS E removeFirst() Retrieve and remove the first element of this deque. This method differs from pollFirst() only in that it throws NoSuchElementException when this deque is empty. boolean removeFirstOccurrence(Object o) Remove the first occurrence of o from this deque. If the deque does not contain o, it is unchanged. Return true when this deque contained o (or equivalently, when this deque changed as a result of the call). This method throws ClassCastException when o’s class prevents o from being added to this deque, and NullPointerException when o contains the null reference and this deque does not permit null elements to be added. The inherited boolean remove(Object o) method is equivalent to this method. E removeLast() Retrieve and remove the last element of this deque. This method differs from pollLast() only in that it throws NoSuchElementException when this deque is empty. boolean removeLastOccurrence(Object o) Remove the last occurrence of o from this deque. If the deque does not contain o, it is unchanged. Return true when this deque contained o (or equivalently, when this deque changed as a result of the call). This method throws ClassCastException when o’s class prevents o from being added to this deque, and NullPointerException when o contains the null reference and this deque does not permit null elements to be added. As Table 5-6 reveals, Deque declares methods to access elements at both ends of the deque. Methods are provided to insert, remove, and examine the element. Each of these methods exists in two forms: one throws an exception when the operation fails, the other returns a special value (either null or false, depending on the operation). The latter form of the insert operation is designed specifically for use with capacity-restricted Deque implementations; in most implementations, insert operations cannot fail. Figure 5-2 reveals a table from Deque’s Java documentation that nicely summarizes both forms of the insert, remove, and examine methods for both the head and the tail. 362 CHAPTER 5 COLLECTING OBJECTS Figure 5-2. Deque declares twelve methods for inserting, removing, and examining elements at the head or tail of a deque. When a deque is used as a queue, FIFO (First-In-First-Out) behavior results. Elements are added at the end of the deque and removed from the beginning. The methods inherited from the Queue interface are precisely equivalent to the Deque methods as indicated in Table 5-7. Table 5-7. Queue and equivalent Deque Methods Queue Method Equivalent Deque Method add(e) addLast(e) offer(e) offerLast(e) remove() removeFirst() poll() pollFirst() element() getFirst() peek() peekFirst() Finally, deques can also be used as LIFO (Last-In-First-Out) stacks. When a deque is used as a stack, elements are pushed and popped from the beginning of the deque. Because a stack’s push(e) method would be equivalent to Deque’s addFirst(e) method, its pop() method would be equivalent to Deque’s removeFirst() method, and its peek() method would be equivalent to Deque’s peekFirst() method, Deque declares the E peek(), E pop(), and void push(E e) stack-oriented convenience methods. ArrayDeque The ArrayDeque class provides a resizable-array implementation of the Deque interface. It prohibits null elements from being added to a deque, and its iterator() method returns fail-fast iterators. ArrayDeque supplies three constructors: • ArrayDeque() creates an empty array list with an initial capacity of 16 elements. 363 CHAPTER 5 COLLECTING OBJECTS • ArrayDeque(Collection<? extends E> c) creates an array deque containing c’s elements in the order in which they are returned by c’s iterator. (The first element returned by c’s iterator becomes the first element, or front of the deque.) NullPointerException is thrown when c contains the null reference. • ArrayDeque(int numElements) creates an empty array deque with an initial capacity sufficient to hold numElements elements. No exception is thrown when the argument passed to numElements is less than or equal to zero. Listing 5-16 demonstrates an array deque. Listing 5-16. Using an array deque as a stack import java.util.ArrayDeque; import java.util.Deque; class ArrayDequeDemo { public static void main(String[] args) { Deque<String> stack = new ArrayDeque<>(); String[] weekdays = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; for (String weekday: weekdays) stack.push(weekday); while (stack.peek() != null) System.out.println(stack.pop()); } } When you run this application, it generates the following output: Saturday Friday Thursday Wednesday Tuesday Monday Sunday Map A map is a group of key/value pairs (also known as entries). Because the key identifies an entry, a map cannot contain duplicate keys. Furthermore, each key can map to at most one value. Maps are described by the Map interface, which has no parent interface, and whose generic type is Map<K,V> (K is the key’s type; V is the value’s type). Table 5-8 describes Map’s methods. 364 CHAPTER 5 COLLECTING OBJECTS Table 5-8. Map-specific Methods Method Description void clear() Remove all elements from this map, leaving it empty. This method throws UnsupportedOperationException when clear() is not supported. boolean containsKey(Object key) Return true when this map contains an entry for the specified key; otherwise, return false. This method throws ClassCastException when key is of an inappropriate type for this map, and NullPointerException when key contains the null reference and this map does not permit null keys. boolean containsValue(Object value) Return true when this map maps one or more keys to value. This method throws ClassCastException when value is of an inappropriate type for this map, and NullPointerException when value contains the null reference and this map does not permit null values. Set<Map.Entry<K,V>> entrySet() Return a Set view of the entries contained in this map. Because this map backs the view, changes that are made to the map are reflected in the set and vice versa. boolean equals(Object o) Compare o with this map for equality. Return true when o is also a map and the two maps represent the same entries; otherwise, return false. V get(Object key) Return the value to which key is mapped, or null when this map contains no entry for key. If this map permits null values, then a return value of null does not necessarily indicate that the map contains no entry for key; it is also possible that the map explicitly maps key to the null reference. The containsKey() method may be used to distinguish between these two cases. This method throws ClassCastException when key is of an inappropriate type for this map, and NullPointerException when key contains the null reference and this map does not permit null keys. int hashCode() Return the hash code for this map. A map’s hash code is defined to be the sum of the hash codes for the entries in the map’s entrySet() view. boolean isEmpty() Return true when this map contains no entries; otherwise, return false. Set<K> keySet() Return a Set view of the keys contained in this map. Because this map backs the view, changes that are made to the map 365 CHAPTER 5 COLLECTING OBJECTS are reflected in the set and vice versa. 366 V put(K key,V value) Associate value with key in this map. If the map previously contained an entry for key, the old value is replaced by value. This method returns the previous value associated with key, or null when there was no entry for key. (The null return value can also indicate that the map previously associated the null reference with key, if the implementation supports null values.) This method throws UnsupportedOperationException when put() is not supported, ClassCastException when key’s or value’s class is not appropriate for this map, IllegalArgumentException when some property of key or value prevents it from being stored in this map, and NullPointerException when key or value contains the null reference and this map does not permit null keys or values. void putAll(Map<? extends K,? extends V> m) Copy all the entries from map m to this map. The effect of this call is equivalent to that of calling put(k, v) on this map once for each mapping from key k to value v in map m. This method throws UnsupportedOperationException when putAll() is not supported, ClassCastException when the class of a key or value in map m is not appropriate for this map, IllegalArgumentException when some property of a key or value in map m prevents it from being stored in this map, and NullPointerException when m contains the null reference or when m contains null keys or values and this map does not permit null keys or values. V remove(Object key) Remove key’s entry from this map if it is present. This method returns the value to which this map previously associated with key, or null when the map contained no entry for key. If this map permits null values, then a return value of null does not necessarily indicate that the map contained no entry for key; it is also possible that the map explicitly mapped key to null. This map will not contain an entry for key once the call returns. This method throws UnsupportedOperationException when remove() is not supported, ClassCastException when the class of key is not appropriate for this map, and NullPointerException when key contains the null reference and this map does not permit null keys. int size() Return the number of key/value entries in this map. If the map contains more than Integer.MAX_VALUE entries, this method returns Integer.MAX_VALUE. CHAPTER 5 COLLECTING OBJECTS Collection<V> values() Return a Collection view of the values contained in this map. Because this map backs the view, changes that are made to the map are reflected in the collection and vice versa. Unlike List, Set, and Queue, Map does not extend Collection. However, it is possible to view a map as a Collection instance by calling Map’s keySet(), values(), and entrySet() methods, which respectively return a Set of keys, a Collection of values, and a Set of key/value pair entries. ■ Note The values() method returns Collection instead of Set because multiple keys can map to the same value, and values() would then return multiple copies of the same value. The Collection views returned by these methods (recall that a Set is a Collection because Set extends Collection) provide the only means to iterate over a Map. For example, suppose you declare Listing 5-17’s Color enum with its three Color constants, RED, GREEN, and BLUE. Listing 5-17. A colorful enum enum Color { RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255); private int r, g, b; private Color(int r, int g, int b) { this.r = r; this.g = g; this.b = b; } @Override public String toString() { return "r = "+r+", g = "+g+", b = "+b; } } The following example declares a map of String keys and Color values, adds several entries to the map, and iterates over the keys and values: Map<String, Color> colorMap = ...; // ... represents creation of a Map implementation colorMap.put("red", Color.RED); colorMap.put("blue", Color.BLUE); colorMap.put("green", Color.GREEN); colorMap.put("RED", Color.RED); for (String colorKey: colorMap.keySet()) 367 CHAPTER 5 COLLECTING OBJECTS System.out.println(colorKey); Collection<Color> colorValues = colorMap.values(); for (Iterator<Color> it = colorValues.iterator(); it.hasNext();) System.out.println(it.next()); When running this example against a hashmap implementation (discussed later) of colorMap, you should observe output similar to the following: red blue green RED r = 255, r = 0, g r = 0, g r = 255, g = = g = 0, 0, b 255, = 0, b = b b = 0 255 = 0 = 0 The first four output lines identify the map’s keys; the second four output lines identify the map’s values. The entrySet() method returns a Set of Map.Entry objects. Each of these objects describes a single entry as a key/value pair and is an instance of a class that implements the Map.Entry interface, where Entry is a nested interface of Map. Table 5-9 describes Map.Entry’s methods. Table 5-9. Map.Entry Methods 368 Method Description boolean equals(Object o) Compare o with this entry for equality. Return true when o is also a map entry and the two entries have the same key and value. K getKey() Return this entry’s key. This method optionally throws IllegalStateException when this entry has previously been removed from the backing map. V getValue() Return this entry’s value. This method optionally throws IllegalStateException when this entry has previously been removed from the backing map. int hashCode() Return this entry’s hash code. V setValue(V value) Replace this entry’s value with value. The backing map is updated with the new value. This method throws UnsupportedOperationException when setValue() is not supported, ClassCastException when value’s class prevents it from being stored in the backing map, NullPointerException when value contains the null reference and the backing map does not permit null, IllegalArgumentException when some property of value prevents it from being stored in the backing map, and (optionally) IllegalStateException when this entry has previously been removed from the backing map. CHAPTER 5 COLLECTING OBJECTS The following example shows you how you might iterate over the previous example’s map entries: for (Map.Entry<String, Color> colorEntry: colorMap.entrySet()) System.out.println(colorEntry.getKey()+": "+colorEntry.getValue()); When running this example against the previously mentioned hashmap implementation, you would observe the following output: red: r = 255, g = 0, b = 0 blue: r = 0, g = 0, b = 255 green: r = 0, g = 255, b = 0 RED: r = 255, g = 0, b = 0 TreeMap The TreeMap class provides a map implementation that is based on a red-black tree. As a result, entries are stored in sorted order of their keys. However, accessing these entries is somewhat slower than with the other Map implementations (which are not sorted) because links must be traversed. ■ Note Check out Wikipedia’s “Red-black tree” entry (http://en.wikipedia.org/wiki/Red-black_tree) to learn about red-black trees. TreeMap supplies four constructors: • TreeMap() creates a new, empty tree map that is sorted according to the natural ordering of its keys. All keys inserted into the map must implement the Comparable interface. • TreeMap(Comparator<? super K> comparator) creates a new, empty tree map that is sorted according to the specified comparator. Passing null to comparator implies that natural ordering will be used. • TreeMap(Map<? extends K, ? extends V> m) creates a new tree map containing m’s entries, sorted according to the natural ordering of its keys. All keys inserted into the new map must implement the Comparable interface. This constructor throws ClassCastException when m’s keys do not implement Comparable or are not mutually comparable, and NullPointerException when m contains the null reference. • TreeMap(SortedMap<K, ? extends V> sm) creates a new tree map containing the same entries and using the same ordering as sm. (I discuss sorted maps later in this chapter.) This constructor throws NullPointerException when sm contains the null reference. Listing 5-18 demonstrates a tree map. 369 CHAPTER 5 COLLECTING OBJECTS Listing 5-18. Sorting a map’s entries according to the natural ordering of their String-based keys import java.util.Map; import java.util.TreeMap; This book was purchased by [email protected] class TreeMapDemo { public static void main(String[] args) { Map<String, Integer> msi = new TreeMap<>(); String[] fruits = {"apples", "pears", "grapes", "bananas", "kiwis"}; int[] quantities = {10, 15, 8, 17, 30}; for (int i = 0; i < fruits.length; i++) msi.put(fruits[i], quantities[i]); for (Map.Entry<String, Integer> entry: msi.entrySet()) System.out.println(entry.getKey()+": "+entry.getValue()); } } When you run this application, it generates the following output: apples: 10 bananas: 17 grapes: 8 kiwis: 30 pears: 15 HashMap The HashMap class provides a map implementation that is based on a hashtable data structure. This implementation supports all Map operations, and permits null keys and null values. It makes no guarantees on the order in which entries are stored. A hashtable maps keys to integer values with the help of a hash function. Java provides this function in the form of Object’s hashCode() method, which classes override to provide appropriate hash codes. A hash code identifies one of the hashtable’s array elements, which is known as a bucket or slot. For some hashtables, the bucket may store the value that is associated with the key. Figure 5-3 illustrates this kind of hashtable. 370 CHAPTER 5 COLLECTING OBJECTS Figure 5-3. A simple hashtable maps keys to buckets that store values associates with those keys. The hash function hashes Bob Doe to 0, which identifies the first bucket. This bucket contains ACCTS, which is Bob Doe’s employee type. The hash function also hashes John Doe and Sally Doe to 1 and 2 (respectively) whose buckets contain SALES. A perfect hash function hashes each key to a unique integer value. However, this ideal is very difficult to meet. In practice, some keys will hash to the same integer value. This nonunique mapping is referred to as a collision. To address collisions, most hashtables associate a linked list of entries with a bucket. Instead of containing a value, the bucket contains the address of the first node in the linked list, and each node contains one of the colliding entries. See Figure 5-4. Figure 5-4. A complex hashtable maps keys to buckets that store references to linked lists whose node values are hashed from the same keys. 371 CHAPTER 5 COLLECTING OBJECTS When storing a value in a hashtable, the hashtable uses the hash function to hash the key to its hash code, and then searches the appropriate linked list to see if an entry with a matching key exists. If there is an entry, its value is updated with the new value. Otherwise, a new node is created, populated with the key and value, and appended to the list. When retrieving a value from a hashtable, the hashtable uses the hash function to hash the key to its hash code, and then searches the appropriate linked list to see if an entry with a matching key exists. If there is an entry, its value is returned. Otherwise, the hashtable may return a special value to indicate that there is no entry, or it might throw an exception. The number of buckets is known as the hashtable’s capacity. The ratio of the number of stored entries divided by the number of buckets is known as the hashtable’s load factor. Choosing the right load factor is important for balancing performance with memory use: • As the load factor approaches 1, the probability of collisions and the cost of handling them (by searching lengthy linked lists) increase. • As the load factor approaches 0, the hashtable’s size in terms of number of buckets increases with little improvement in search cost. • For many hashtables, a load factor of 0.75 is close to optimal. This value is the default for HashMap’s hashtable implementation. HashMap supplies four constructors: • HashMap() creates a new, empty hashmap with an initial capacity of 16 and a load factor of 0.75. • HashMap(int initialCapacity) creates a new, empty hashmap with a capacity specified by initialCapacity and a load factor of 0.75. This constructor throws IllegalArgumentException when initialCapacity’s value is less than 0. • HashMap(int initialCapacity, float loadFactor) creates a new, empty hashmap with a capacity specified by initialCapacity and a load factor specified by loadFactor. This constructor throws IllegalArgumentException when initialCapacity is less than 0 or when loadFactor is less than or equal to 0. • HashMap(Map<? extends K, ? extends V> m) creates a new hashmap containing m’s entries. This constructor throws NullPointerException when m contains the null reference. Listing 5-19 demonstrates a hashmap. Listing 5-19. Using a hashmap to count command-line arguments import java.util.HashMap; import java.util.Map; class HashMapDemo { public static void main(String[] args) { Map<String, Integer> argMap = new HashMap<>(); for (String arg: args) { Integer count = argMap.get(arg); 372 CHAPTER 5 COLLECTING OBJECTS argMap.put(arg, (count == null) ? 1 : count+1); } System.out.println(argMap); System.out.println("Number of distinct arguments = "+argMap.size()); } } HashMapDemo creates a hashmap of String keys and Integer values. Each key is one of the commandline arguments passed to this application, and its value is the number of occurrences of that argument on the command line. For example, java HashMapDemo how much wood could a woodchuck chuck if a woodchuck could chuck wood generates the following output: {wood=2, could=2, how=1, if=1, chuck=2, a=2, woodchuck=2, much=1} Number of distinct arguments = 8 Because the String class overrides equals() and hashCode(), Listing 5-19 can use String objects as keys in a hashmap. When you create a class whose instances are to be used as keys, you must ensure that you override both methods. Listing 5-6 showed you that a class’s overriding hashCode() method can call a reference field’s hashCode() method and return its value, provided that the class declares a single reference field (and no primitive type fields). More commonly, classes declare multiple fields, and a better implementation of the hashCode() method is required. The implementation should try to generate hash codes that minimize collisions. There is no rule on how to best implement hashCode(), and various algorithms (recipes for accomplishing tasks) have been created. My favorite algorithm appears in Effective Java Second Edition, by Joshua Bloch (Addison-Wesley, 2008; ISBN: 0321356683). The following algorithm, which assumes the existence of an arbitrary class that is referred to as X, closely follows Bloch’s algorithm, but is not identical: 1. Initialize int variable hashCode (the name is arbitrary) to an arbitrary nonzero integer value, such as 19. This variable is initialized to a nonzero value to ensure that it takes into account any initial fields whose hash codes are zeros. If you initialize hashCode to 0, the final hash code will be unaffected by such fields and you run the risk of increased collisions. 2. For each field f that is also used in X’s equals() method, calculate f’s hash code and assign it to int variable hc as follows: a. If f is of Boolean type, calculate hc = f?1:0. b. If f is of byte integer, character, integer, or short integer type, calculate hc = (int) f. The integer value is the hash code. c. If f is of long integer type, calculate hc = (int) (f^(f>>>32)). This expression exclusive ORs the long integer’s least significant 32 bits with its most significant 32 bits. d. If f is of type floating-point, calculate hc = Float.floatToIntBits(f). This method takes +infinity, -infinity, and NaN into account. e. If f is of type double precision floating-point, calculate long l = Double.doubleToLongBits(f); hc = (int) (l^(l>>>32)). f. If f is a reference field with a null reference, calculate hc = 0. 373 CHAPTER 5 COLLECTING OBJECTS g. If f is a reference field with a nonnull reference, and if X’s equals() method compares the field by recursively calling equals() (as in Listing 5-12’s Employee class), calculate hc = f.hashCode(). However, if equals() employs a more complex comparison, create a canonical (simplest possible) representation of the field and call hashCode() on this representation. h. If f is an array, treat each element as a separate field by applying this algorithm recursively and combining the hc values as shown in the next step. 3. Combine hc with hashCode as follows: hashCode = hashCode*31+hc. Multiplying hashCode by 31 makes the resulting hash value dependent on the order in which fields appear in the class, which improves the hash value when a class contains multiple fields that are similar (several ints, for example). I chose 31 to be consistent with the String class’s hashCode() method. 4. Return hashCode from hashCode(). ■ Tip Instead of using this or another algorithm to create a hash code, you might find it easier to work with the HashCodeBuilder class (see http://commons.apache.org/lang/api2.4/org/apache/commons/lang/builder/HashCodeBuilder.html for an explanation of this class). This class, which follows Bloch’s rules, is part of the Apache Commons Lang component, which you can download from http://commons.apache.org/lang/. In Chapter 2, Listing 2-27’s Point class overrides equals() but does not override hashCode(). I later presented a small code fragment that must be appended to Point’s main() method to demonstrate the problem of not overriding hashCode(). I restate this problem here: Although objects p1 and Point(10, 20) are logically equivalent, these objects have different hash codes, resulting in each object referring to a different entry in the hashmap. If an object is not stored (via put()) in that entry, get() returns null. Listing 5-20 modifies Listing 2-27’s Point class by declaring a hashCode() method. This method uses the aforementioned algorithm to ensure that logically equivalent Point objects hash to the same entry. Listing 5-20. Using a hashmap to count command-line arguments import java.util.HashMap; import java.util.Map; class Point { private int x, y; Point(int x, int y) { this.x = x; this.y = y; } int getX() 374 CHAPTER 5 COLLECTING OBJECTS { return x; } int getY() { return y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return p.x == x && p.y == y; } @Override public int hashCode() { int hashCode = 19; int hc = x; hashCode = hashCode*31+hc; hc = y; hashCode = hashCode*31+hc; return hc; } public static void main(String[] args) { Point p1 = new Point(10, 20); Point p2 = new Point(20, 30); Point p3 = new Point(10, 20); // Test reflexivity System.out.println(p1.equals(p1)); // Output: true // Test symmetry System.out.println(p1.equals(p2)); // Output: false System.out.println(p2.equals(p1)); // Output: false // Test transitivity System.out.println(p2.equals(p3)); // Output: false System.out.println(p1.equals(p3)); // Output: true // Test nullability System.out.println(p1.equals(null)); // Output: false // Extra test to further prove the instanceof operator's usefulness. System.out.println(p1.equals("abc")); // Output: false Map<Point, String> map = new HashMap<Point, String>(); map.put(p1, "first point"); System.out.println(map.get(p1)); // Output: first point System.out.println(map.get(new Point(10, 20))); // Output: null } } Listing 5-20’s hashCode() method is a little verbose in that it assigns each of x and y to local variable hc, rather than directly using these fields in the hash code calculation. However, I decided to follow this approach to more closely mirror the hash code algorithm. 375 CHAPTER 5 COLLECTING OBJECTS When you run this application, its last two lines of output are of the most interest. Instead of presenting first point followed by null on two separate lines, the application now correctly presents first point followed by first point on these lines. ■ Note LinkedHashMap is a subclass of HashMap that uses a linked list to store its entries. As a result, LinkedHashMap’s iterator returns entries in the order in which they were inserted. For example, if Listing 5-19 had specified Map<String, Integer> argMap = new LinkedHashMap<>();, the application’s output for java HashMapDemo how much wood could a woodchuck chuck if a woodchuck could chuck wood would have been {how=1, much=1, wood=2, could=2, a=2, woodchuck=2, chuck=2, if=1} followed by Number of distinct arguments = 8. IdentityHashMap The IdentityHashMap class provides a Map implementation that uses reference equality (==) instead of object equality (equals()) when comparing keys and values. This is an intentional violation of Map’s general contract, which mandates the use of equals() when comparing elements. IdentityHashMap obtains hash codes via System’s static int identityHashCode(Object x) method instead of via each key’s hashCode() method. identityHashCode() returns the same hash code for x as returned by Object’s hashCode() method, whether or not x’s class overrides hashCode(). The hash code for the null reference is zero. These characteristics give IdentityHashMap a performance advantage over other Map implementations. Also, IdentityHashMap supports mutable keys (objects used as keys and whose hash codes change when their field values change while in the map). Listing 5-21 contrasts IdentityHashMap with HashMap where mutable keys are concerned. Listing 5-21. Contrasting IdentityHashMap with HashMap in a mutable key context import java.util.IdentityHashMap; import java.util.HashMap; import java.util.Map; class IdentityHashMapDemo { public static void main(String[] args) { Map<Employee, String> map1 = new IdentityHashMap<>(); Map<Employee, String> map2 = new HashMap<>(); Employee e1 = new Employee("John Doe", 28); map1.put(e1, "SALES"); System.out.println(map1); Employee e2 = new Employee("Jane Doe", 26); map2.put(e2, "MGMT"); System.out.println(map2); System.out.println("map1 contains key e1 = "+map1.containsKey(e1)); System.out.println("map2 contains key e2 = "+map2.containsKey(e2)); 376 CHAPTER 5 COLLECTING OBJECTS e1.setAge(29); e2.setAge(27); System.out.println(map1); System.out.println(map2); System.out.println("map1 contains key e1 = "+map1.containsKey(e1)); System.out.println("map2 contains key e2 = "+map2.containsKey(e2)); } } class Employee { private String name; private int age; Employee(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (!(o instanceof Employee)) return false; Employee e = (Employee) o; return e.name.equals(name) && e.age == age; } @Override public int hashCode() { int hashCode = 19; hashCode = hashCode*31+name.hashCode(); hashCode = hashCode*31+age; return hashCode; } void setAge(int age) { this.age = age; } void setName(String name) { this.name = name; } @Override public String toString() { return name+" "+age; } } Listing 5-21’s main() method creates IdentityHashMap and HashMap instances that each store an entry consisting of an Employee key and a String value. Because Employee instances are mutable (because of setAge() and setName()), main() changes their ages while these keys are stored in their maps. These changes result in the following output: 377 CHAPTER 5 COLLECTING OBJECTS {John Doe 28=SALES} {Jane Doe 26=MGMT} map1 contains key e1 map2 contains key e2 {John Doe 29=SALES} {Jane Doe 27=MGMT} map1 contains key e1 map2 contains key e2 = true = true = true = false The last four lines show that the changed entries remain in their maps. However, map2’s containsKey() method reports that its HashMap instance no longer contains its Employee key (which should be Jane Doe 27), whereas map1’s containsKey() method reports that its IdentityHashMap instance still contains its Employee key, which is now John Doe 29. ■ Note IdentityHashMap’s documentation states that “a typical use of this class is topology-preserving object graph transformations, such as serialization or deep copying.” (I discuss serialization in Chapter 8.) It also states the following: “another typical use of this class is to maintain proxy objects.” Also, developers responding to stackoverflow’s “Use Cases for Identity HashMap” topic (http://stackoverflow.com/questions/838528/usecases-for-identity-hashmap) mention that it is much faster to use IdentityHashMap than HashMap when the keys are Class objects. WeakHashMap The WeakHashMap class provides a Map implementation that is based on weakly reachable keys. Because each key object is stored indirectly as the referent of a weak reference, the key is automatically removed from the map only after the garbage collector clears all weak references to the key (inside and outside of the map). ■ Note Check out Chapter 4’s “Reference API” section to learn about weakly reachable and weak references. In contrast, value objects are stored via strong references (and should not strongly refer to their own keys, either directly or indirectly, because doing so prevents their associated keys from being discarded). When a key is removed from a map, its associated value object is also removed. Listing 5-22 provides a simple demonstration of the WeakHashMap class. Listing 5-22. Detecting a weak hashmap entry’s removal import java.util.Map; import java.util.WeakHashMap; class LargeObject 378 CHAPTER 5 COLLECTING OBJECTS { private byte[] memory = new byte[1024*1024*50]; // 50 megabytes } class WeakHashMapDemo { public static void main(String[] args) { Map<LargeObject, String> map = new WeakHashMap<>(); LargeObject lo = new LargeObject(); map.put(lo, "Large Object"); System.out.println(map); lo = null; while (!map.isEmpty()) { System.gc(); new LargeObject(); } System.out.println(map); } } Listing 5-22’s main() method stores a 50MB LargeObject key and a String value in the weak hashmap, and then removes the key’s strong reference by assigning null to lo. main() next enters a while loop that executes until the map is empty (map.isEmpty() returns true). Each loop iteration begins with a System.gc() method call, which may or may not cause a garbage collection to take place (depending upon platform). To encourage a garbage collection, the iteration then creates a LargeObject object and throws away its reference. This activity should eventually cause the garbage collector to run and remove the map’s solitary entry. When I run this application on my Windows XP platform, I observe the following output—you might need to modify the code if you find that the application is in an infinite loop: {[email protected]=Large Object} {} ■ Note WeakHashMap is useful for avoiding memory leaks, as explained in Brian Goetz’s article “Java Theory and Practice: Plugging Memory Leaks with Weak References” (http://www.ibm.com/developerworks/java/library/j-jtp11225/). EnumMap The EnumMap class provides a Map implementation whose keys are the members of the same enum. Null keys are not permitted; any attempt to store a null key results in a thrown NullPointerException. Because an enum map is represented internally as an array, an enum map approaches an array in terms of performance. EnumMap supplies the following constructors: 379 CHAPTER 5 COLLECTING OBJECTS • EnumMap(Class<K> keyType) creates an empty enum map with the specified keyType. This constructor throws NullPointerException when keyType contains the null reference. • EnumMap(EnumMap<K,? extends V> m) creates an enum map with the same key type as m, and with m’s entries. This constructor throws NullPointerException when m contains the null reference. • EnumMap(Map<K,? extends V> m) creates an enum map initialized with m’s entries. If m is an EnumMap instance, this constructor behaves like the previous constructor. Otherwise, m must contain at least one entry in order to determine the new enum map’s key type. This constructor throws NullPointerException when m contains the null reference, and IllegalArgumentException when m is not an EnumMap instance and is empty. Listing 5-23 demonstrates EnumMap. Listing 5-23. An enum map of Coin constants This book was purchased by [email protected] import java.util.EnumMap; import java.util.Map; enum Coin { PENNY, NICKEL, DIME, QUARTER } class EnumMapDemo { public static void main(String[] args) { Map<Coin, Integer> map = new EnumMap<>(Coin.class); map.put(Coin.PENNY, 1); map.put(Coin.NICKEL, 5); map.put(Coin.DIME, 10); map.put(Coin.QUARTER, 25); System.out.println(map); Map<Coin,Integer> mapCopy = new EnumMap<>(map); System.out.println(mapCopy); } } When you run this application, it generates the following output: {PENNY=1, NICKEL=5, DIME=10, QUARTER=25} {PENNY=1, NICKEL=5, DIME=10, QUARTER=25} SortedMap TreeMap is an example of a sorted map, which is a map that maintains its entries in ascending order, sorted according to the keys’ natural ordering or according to a comparator that is supplied when the sorted map is created. Sorted maps are described by the SortedMap interface. 380 CHAPTER 5 COLLECTING OBJECTS SortedMap, whose generic type is SortedMap<K,V>, extends Map. With two exceptions, the methods it inherits from Map behave identically on sorted maps as on other maps: • The Iterator instance returned by the iterator() method on any of the sorted map’s Collection views traverses the collections in order. • The arrays returned by the Collection views’ toArray() methods contain the keys, values, or entries in order. ■ Note Although not guaranteed, the toString() methods of the Collection views of SortedSet implementations in the Collections Framework (such as TreeMap) return a string containing all of the view’s elements in order. SortedMap’s documentation requires that an implementation must provide the four standard constructors that I presented in my discussion of TreeMap. Furthermore, implementations of this interface must implement the methods that are described in Table 5-10. Table 5-10. SortedMap-specific Methods Method Description Comparator<? super K> comparator() Return the comparator used to order the keys in this map, or null when this map uses the natural ordering of its keys. Set<Map.Entry<K,V>> entrySet() Return a Set view of the mappings contained in this map. The set’s iterator returns these entries in ascending key order. Because this map backs the view, changes that are made to the map are reflected in the set and vice versa. K firstKey() Return the first (lowest) key currently in this map, or throw a NoSuchElementException instance when this map is empty. SortedMap<K,V> headMap(K toKey) Return a view of that portion of this map whose keys are strictly less than toKey. Because this map backs the returned map, changes in the returned map are reflected in this map and vice versa. The returned map supports all optional map operations that this map supports. This method throws ClassCastException when toKey is not compatible with this map’s comparator (or, when the map has no comparator, when toKey does not implement Comparable), NullPointerException when toKey is null and this map does not permit null keys, and IllegalArgumentException when this map has a restricted range and toKey lies outside of this range’s bounds. 381 CHAPTER 5 COLLECTING OBJECTS Set<K> keySet() Return a Set view of the keys contained in this map. The set’s iterator returns the keys in ascending order. Because the map backs the view, changes that are made to the map are reflected in the set and vice versa. K lastKey() Return the last (highest) key currently in this map, or throw a NoSuchElementException instance when this map is empty. SortedMap<K,V> subMap(K fromKey, K toKey) Return a view of the portion of this map whose keys range from fromKey, inclusive, to toKey, exclusive. (When fromKey and toKey are equal, the returned map is empty.) Because this map backs the returned map, changes in the returned map are reflected in this map and vice versa. The returned map supports all optional map operations that this map supports. This method throws ClassCastException when fromKey and toKey cannot be compared to one another using this map’s comparator (or, when the map has no comparator, using natural ordering), NullPointerException when fromKey or toKey is null and this map does not permit null keys, and IllegalArgumentException when fromKey is greater than toKey or when this map has a restricted range and fromKey or toKey lies outside its bounds. SortedMap<K,V> tailMap(K fromKey) Return a view of that portion of this map whose keys are greater than or equal to fromKey. Because this map backs the returned map, changes in the returned map are reflected in this map and vice versa. The returned map supports all optional map operations that this map supports. This method throws ClassCastException when fromKey is not compatible with this map’s comparator (or, when the map has no comparator, when fromKey does not implement Comparable), NullPointerException when fromKey is null and this map does not permit null keys, and IllegalArgumentException when this map has a restricted range and fromKey lies outside of the range’s bounds. Collection<V> values() Return a Collection view of the values contained in this map. The collection’s iterator returns the values in ascending order of the corresponding keys. Because the map backs the collection, changes that are made to the map are reflected in the collection and vice versa. Listing 5-24 demonstrates a sorted map based on a tree map. Listing 5-24. A sorted map of office supply names and quantities import java.util.Comparator; import java.util.SortedMap; import java.util.TreeMap; 382 CHAPTER 5 COLLECTING OBJECTS class SortedMapDemo { public static void main(String[] args) { SortedMap<String, Integer> smsi = new TreeMap<>(); String[] officeSupplies = { "pen", "pencil", "legal pad", "CD", "paper" }; int[] quantities = { 20, 30, 5, 10, 20 }; for (int i = 0; i < officeSupplies.length; i++) smsi.put(officeSupplies[i], quantities[i]); System.out.println(smsi); System.out.println(smsi.headMap("pencil")); System.out.println(smsi.headMap("paper")); SortedMap<String, Integer> smsiCopy; Comparator<String> cmp; cmp = new Comparator<String>() { public int compare(String key1, String key2) { return key2.compareTo(key1); // descending order } }; smsiCopy = new TreeMap<String, Integer>(cmp); smsiCopy.putAll(smsi); System.out.println(smsiCopy); } } When you run this application (java SortedMapDemo), it generates the following output: {CD=10, legal pad=5, paper=20, pen=20, pencil=30} {CD=10, legal pad=5, paper=20, pen=20} {CD=10, legal pad=5} {pencil=30, pen=20, paper=20, legal pad=5, CD=10} NavigableMap TreeMap is an example of a navigable map, which is a sorted map that can be iterated over in descending order as well as ascending order, and which can report closest matches for given search targets. Navigable maps are described by the NavigableMap interface, whose generic type is NavigableMap<K,V>, which extends SortedMap, and which is described in Table 5-11. 383 CHAPTER 5 COLLECTING OBJECTS Table 5-11. NavigableMap-specific Methods 384 Method Description Map.Entry<K,V> ceilingEntry(K key) Return the key-value mapping associated with the least key greater than or equal to key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. K ceilingKey(K key) Return the least key greater than or equal to key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. NavigableSet<K> descendingKeySet() Return a reverse order navigable set-based view of the keys contained in this map. The set’s iterator returns the keys in descending order. This map backs the set, so changes to the map are reflected in the set and vice versa. If the map is modified (except through the iterator’s own remove() operation) while iterating over the set, the results of the iteration are undefined. NavigableMap<K,V> descendingMap() Return a reverse order view of the mappings contained in this map. This map backs the descending map, so changes to the map are reflected in the descending map and vice versa. If either map is modified while iterating over a collection view of either map (except through the iterator’s own remove() operation), the results of the iteration are undefined. Map.Entry<K,V> firstEntry() Return a key-value mapping associated with the least key in this map, or null when the map is empty. Map.Entry<K,V> floorEntry(K key) Return a key-value mapping associated with the greatest key less than or equal to key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. K floorKey(K key) Return the greatest key less than or equal to key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. CHAPTER 5 COLLECTING OBJECTS NavigableMap<K,V> headMap(K toKey, boolean inclusive) Return a view of the portion of this map whose keys are less than (or equal to, when inclusive is true) toKey. This map backs the returned map, so changes in the returned map are reflected in this map and vice versa. The returned map supports all optional map operations that this map supports. This method throws ClassCastException when toKey is not compatible with this map’s comparator (or, when the map has no comparator, when toMap does not implement Comparable), NullPointerException when toMap is null and this map does not permit null keys, and IllegalArgumentException when this map has a restricted range and toKey lies outside of this range’s bounds. Map.Entry<K,V> higherEntry(K key) Return a key-value mapping associated with the least key strictly greater than key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. K higherKey(K key) Return the least key strictly greater than key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. Map.Entry<K,V> lastEntry() Return a key-value mapping associated with the greatest key in this map, or null when the map is empty. Map.Entry<K,V> lowerEntry(K key) Return a key-value mapping associated with the greatest key strictly less than key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. K lowerKey(K key) Return the greatest key strictly less than key, or null when there is no such key. This method throws ClassCastException when key cannot be compared with the keys currently in the map, and NullPointerException when key is null and this map does not permit null keys. NavigableSet<K> navigableKeySet() Return a navigable set-based view of the keys contained in this map. The set’s iterator returns the keys in ascending order. This map backs the set, so changes to the map are reflected in the set and vice versa. If the map is modified while iterating over the set (except through the iterator’s own remove() operation), the results of the iteration are 385 CHAPTER 5 COLLECTING OBJECTS undefined. Map.Entry<K,V> pollFirstEntry() Remove and return a key-value mapping associated with the least key in this map, or null when the map is empty. Map.Entry<K,V> pollLastEntry() Remove and return a key-value mapping associated with the greatest key in this map, or null when the map is empty. NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) Return a view of the portion of this map whose keys range from fromKey to toKey. (When fromKey and toKey are equal, the returned map is empty unless fromInclusive and toInclusive are both true.) This map backs the returned map, so changes in the returned map are reflected in this map and vice versa. The returned map supports all optional map operations that this map supports. This method throws ClassCastException when fromKey and toKey cannot be compared to one another using this map’s comparator (or, when the map has no comparator, using natural ordering), NullPointerException when fromKey or toKey is null and this map does not permit null elements, and IllegalArgumentException when fromKey is greater than toKey or when this map has a restricted range and fromKey or toMap lies outside of this range’s bounds. NavigableMap<K,V> tailMap(K fromKey, boolean inclusive) Return a view of the portion of this map whose keys are greater than (or equal to, when inclusive is true) fromKey. This map backs the returned map, so changes in the returned map are reflected in this map and vice versa. The returned map supports all optional map operations that this map supports. This method throws ClassCastException when fromKey is not compatible with this map’s comparator (or, when the map has no comparator, when fromKey does not implement Comparable), NullPointerException when fromKey is null and this map does not permit null keys, and IllegalArgumentException when this map has a restricted range and fromKey lies outside of this range’s bounds. Table 5-11’s methods describe the NavigableMap equivalents of the NavigableSet methods presented in Table 5-4, and even return NavigableSet instances in two instances. Listing 5-25 demonstrates a navigable map based on a tree map. Listing 5-25. Navigating a map of (bird, count within a small acreage) entries import import import import java.util.Iterator; java.util.NavigableMap; java.util.NavigableSet; java.util.TreeMap; class NavigableMapDemo 386 CHAPTER 5 COLLECTING OBJECTS { public static void main(String[] args) { NavigableMap<String,Integer> nm = new TreeMap<>(); String[] birds = { "sparrow", "bluejay", "robin" }; int[] ints = { 83, 12, 19 }; for (int i = 0; i < birds.length; i++) nm.put(birds[i], ints[i]); System.out.println("Map = "+nm); System.out.print("Ascending order of keys: "); NavigableSet<String> ns = nm.navigableKeySet(); Iterator iter = ns.iterator(); while (iter.hasNext()) System.out.print(iter.next()+" "); System.out.println(); System.out.print("Descending order of keys: "); ns = nm.descendingKeySet(); iter = ns.iterator(); while (iter.hasNext()) System.out.print(iter.next()+" "); System.out.println(); System.out.println("First entry = "+nm.firstEntry()); System.out.println("Last entry = "+nm.lastEntry()); System.out.println("Entry < ostrich is "+nm.lowerEntry("ostrich")); System.out.println("Entry > crow is "+nm.higherEntry("crow")); System.out.println("Poll first entry: "+nm.pollFirstEntry()); System.out.println("Map = "+nm); System.out.println("Poll last entry: "+nm.pollLastEntry()); System.out.println("Map = "+nm); } } Listing 5-25’s System.out.println("Map = "+nm); method calls rely on TreeMap’s toString() method to obtain the contents of a navigable map. When you run this application, you observe the following output: Map = {bluejay=12, robin=19, sparrow=83} Ascending order of keys: bluejay robin sparrow Descending order of keys: sparrow robin bluejay First entry = bluejay=12 Last entry = sparrow=83 Entry < ostrich is bluejay=12 Entry > crow is robin=19 Poll first entry: bluejay=12 Map = {robin=19, sparrow=83} Poll last entry: sparrow=83 Map = {robin=19} 387 CHAPTER 5 COLLECTING OBJECTS Utilities The Collections Framework would not be complete without its Arrays and Collections utility classes. Each class supplies various class methods that implement useful algorithms in the contexts of arrays and collections. Following is a sampling of the Arrays class’s array-oriented utility methods: • static <T> List<T> asList(T... a) returns a fixed-size list backed by array a. (Changes to the returned list “write through” to the array.) For example, List<String> birds = Arrays.asList("Robin", "Oriole", "Bluejay"); converts the three-element array of Strings (recall that a variable sequence of arguments is implemented as an array) to a List whose reference is assigned to birds. • static int binarySearch(int[] a, int key) searches array a for entry key using the binary search algorithm (explained following this list). The array must be sorted before calling this method; otherwise, the results are undefined. This method returns the index of the search key, if it is contained in the array; otherwise, (-(insertion point)-1) is returned. The insertion point is the point at which key would be inserted into the array (the index of the first element greater than key, or a.length if all elements in the array are less than key) and guarantees that the return value will be greater than or equal to 0 if and only if key is found. For example, Arrays.binarySearch(new String[] {"Robin", "Oriole", "Bluejay"}, "Oriole") returns 1, "Oriole"’s index. • static void fill(char[] a, char ch) stores ch in each element of the specified character array. For example, Arrays.fill(screen[i], ' '); fills the ith row of a 2D screen array with spaces. • static void sort(long[] a) sorts the elements in long integer array a into ascending numerical order; for example, long lArray = new long[] { 20000L, 89L, 66L, 33L}; Arrays.sort(lArray);. • static <T> void sort(T[] a, Comparator<? super T> c) sorts the elements in array a using comparator c to order them. For example, when given Comparator<String> cmp = new Comparator<String>() { public int compare(String e1, String e2) { return e2.compareTo(e1); } }; String[] innerPlanets = { "Mercury", "Venus", "Earth", "Mars" };, Arrays.sort(innerPlanets, cmp); uses cmp to help in sorting innerPlanets into descending order of its elements: Venus, Mercury, Mars, Earth is the result. There are two common algorithms for searching an array for a specific element. Linear search searches the array element by element from index 0 to the index of the searched-for element or the end of the array. On average, half of the elements must be searched; larger arrays take longer to search. However, the arrays do not need to be sorted. In contrast, binary search searches ordered array a’s n items for element e in a much faster amount of time. It works by recursively performing the following steps: 388 1. Set low index to 0. 2. Set high index to n-1. 3. If low index > high index, then Print “Unable to find ” e. End. CHAPTER 5 COLLECTING OBJECTS 4. Set middle index to (low index+high index)/2. 5. If e > a[middle index], then set low index to middle index+1. Go to 3. 6. If e < a[middle index], then set high index to middle index-1. Go to 3. 7. Print “Found ” e “ at index ” middle index. The algorithm is similar to optimally looking for a name in a phone book. Start by opening the book to the exact middle. If the name is not on that page, proceed to open the book to the exact middle of the first half or the second half, depending on in which half the name occurs. Repeat until you find the name (or not). Applying a linear search to 4,000,000,000 elements results in approximately 2,000,000,000 comparisons (on average), which takes time. In contrast, applying a binary search to 4,000,000,000 elements results in a maximum of 32 comparisons. This is why Arrays contains binarySearch() methods and not also linearSearch() methods. Following is a sampling of the Collections class’s collection-oriented class methods: • static <T extends Object&Comparable<? super T>> T min(Collection<? extends T> c) returns the minimum element of collection c according to the natural ordering of its elements. For example, System.out.println(Collections.min(Arrays.asList(10, 3, 18, 25))); outputs 3. All of c’s elements must implement the Comparable interface. Furthermore, all elements must be mutually comparable. This method throws NoSuchElementException when c is empty. • static void reverse(List<?> l) reverses the order of list l’s elements. For example, List<String> birds = Arrays.asList("Robin", "Oriole", "Bluejay"); Collections.reverse(birds); System.out.println(birds); results in [Bluejay, Oriole, Robin] as the output. • static <T> List<T> singletonList(T o) returns an immutable list containing only object o. For example, list.removeAll(Collections.singletonList(null)); removes all null elements from list. • static <T> Set<T> synchronizedSet(Set<T> s) returns a synchronized (threadsafe) set backed by set s; for example, Set<String> ss = Collections.synchronizedSet(new HashSet<String>());. In order to guarantee serial access, it is critical that all access to the backing set is accomplished through the returned set. • static <K,V> Map<K,V> unmodifiableMap(Map<? extends K,? extends V> m) returns an unmodifiable view of map m; for example, Map<String, Integer> msi = Collections.synchronizedMap(new HashMap<String, Integer>());. Query operations on the returned map “read through” to the specified map, and attempts to modify the returned map, whether direct or via its collection views, result in an UnsupportedOperationException. ■ Note For performance reasons, collections implementations are unsynchronized—unsynchronized collections have better performance than synchronized collections. To use a collection in a multithreaded context, however, 389 CHAPTER 5 COLLECTING OBJECTS you need to obtain a synchronized version of that collection. You obtain that version by calling a method such as synchronizedSet(). You might be wondering about the purpose for the various “empty” class methods in the Collections class. For example, static final <T> List<T> emptyList() returns an immutable empty list, as in List<String> ls = Collections.emptyList();. These methods are present because they offer a useful alternative to returning null (and avoiding potential NullPointerExceptions) in certain contexts. Consider Listing 5-26. Listing 5-26. Empty and nonempty Lists of Birds import import import import java.util.ArrayList; java.util.Collections; java.util.Iterator; java.util.List; This book was purchased by [email protected] class Birds { private List<String> birds; Birds() { birds = Collections.emptyList(); } Birds(String... birdNames) { birds = new ArrayList<String>(); for (String birdName: birdNames) birds.add(birdName); } @Override public String toString() { return birds.toString(); } } class EmptyListDemo { public static void main(String[] args) { Birds birds = new Birds(); System.out.println(birds); birds = new Birds("Swallow", "Robin", "Bluejay", "Oriole"); System.out.println(birds); } } 390 CHAPTER 5 COLLECTING OBJECTS Listing 5-26 declares a Birds class that stores the names of various birds in a list. This class provides two constructors, a noargument constructor and a constructor that takes a variable number of String arguments identifying various birds. The noargument constructor invokes emptyList() to initialize its private birds field to an empty List of String—emptyList() is a generic method and the compiler infers its return type from its context. If you’re wondering about the need for emptyList(), look at the toString() method. Notice that this method evaluates birds.toString(). If we did not assign a reference to an empty List<String> to birds, birds would contain null (the default value for this instance field when the object is created), and a NullPointerException instance would be thrown when attempting to evaluate birds.toString(). When you run this application (java EmptyListDemo), it generates the following output: [] [Swallow, Robin, Bluejay, Oriole] The emptyList() method is implemented as follows: return (List<T>) EMPTY_LIST;. This statement returns the single List instance assigned to the EMPTY_LIST class field in the Collections class. You might want to work with EMPTY_LIST directly, but you’ll run into an unchecked warning message if you do, because EMPTY_LIST is declared to be of the raw type List, and mixing raw types with generic types leads to such messages. Although you could suppress the warning, you’re better off using the emptyList() method. Suppose you add a void setBirds(List<String> birds) method to Birds, and pass an empty list to this method, as in birds.setBirds(Collections.emptyList());. The compiler will respond with an error message stating that it requires the argument to be of type List<String>, but instead the argument is of type List<Object>. It does so because the compiler cannot figure out the proper type from this context, and so it chooses List<Object>. There is a way to solve this problem, which will probably look very strange. Specify birds.setBirds(Collections.<String>emptyList());, where the formal type parameter list and its actual type argument appear after the member access operator and before the method name. The compiler will now know that the proper type argument is String, and that emptyList() is to return List<String>. Legacy Collections APIs Java 1.2 introduced the Collections Framework. Prior to the framework’s inclusion in Java, developers had two choices where collections were concerned: create their own frameworks, or use the Vector, Enumeration, Stack, Dictionary, Hashtable, Properties, and BitSet types, which were introduced by Java 1.0. Vector is a concrete class that describes a growable array, much like ArrayList. Unlike an ArrayList instance, a Vector instance is synchronized. Vector has been generified and also retrofitted to support the Collections Framework, which makes statements such as List<String> list = new Vector<String>(); legal. The Collections Framework provides Iterator for iterating over a collection’s elements. In contrast, Vector’s elements() method returns an instance of a class that implements the Enumeration interface for enumerating (iterating over and returning) a Vector instance’s elements via Enumeration’s hasMoreElements() and nextElement() methods. Vector is subclassed by the concrete Stack class, which represents a LIFO data structure. Stack provides an E push(E item) method for pushing an object onto the stack, an E pop() method for popping an item off the top of the stack, and a few other methods, such as boolean empty() for determining whether or not the stack is empty. Stack is a good example of bad API design. By inheriting from Vector, it is possible to call Vector’s void add(int index, E element) method to add an element anywhere you wish, and violate a Stack 391 CHAPTER 5 COLLECTING OBJECTS instance’s integrity. In hindsight, Stack should have used composition in its design: use a Vector instance to store a Stack instance’s elements. Dictionary is an abstract superclass for subclasses that map keys to values. The concrete Hashtable class is Dictionary’s only subclass. As with Vector, HashTable instances are synchronized, HashTable has been generified, and HashTable has been retrofitted to support the Collections Framework. Hashtable is subclassed by Properties, a concrete class representing a persistent set of properties (String-based key/value pairs that identify application settings). Properties provides Object setProperty(String key, String value) for storing a property, and public String getProperty(String key) for returning a property’s value. ■ Note Application’s use properties for various purposes. For example, if your application has a graphical user interface, you might persist its main window’s screen location and size to a file via a Properties object so that the application can restore the window’s location and size when it next runs. Properties is another good example of bad API design. By inheriting from Hashtable, you can call Hashtable’s V put(K key, V value) method to store an entry with a non-String key and/or a non-String value. In hindsight, Properties should have leveraged composition: store a Properties instance’s elements in a Hashtable instance. ■ Note Chapter 2 discusses wrapper classes, which is how Stack and Properties should have been implemented. Finally, BitSet is a concrete class that describes a variable-length set of bits. This class’s ability to represent bitsets of arbitrary length contrasts with the previously described integer-based, fixed-length bitset that is limited to a maximum number of members: 32 members for an int-based bitset, or 64 members for a long-based bitset. BitSet provides a pair of constructors for initializing a BitSet instance: BitSet() initializes the instance to initially store an implementation-dependent number of bits, whereas BitSet(int nbits) initializes the instance to initially store nbits bits. BitSet also provides various methods, including the following: 392 • void and(BitSet bs) bitwise ANDs this bitset with bs. This bitset is modified such that a bit is set to 1 when it and the bit at the same position in bs are 1. • void andNot(BitSet bs) sets all the bits in this bitset to 0 whose corresponding bits are set to 1 in bs. • void clear() sets all the bits in this bitset to 0. • Object clone() clones this bitset to produce a new bitset. The clone has exactly the same bits set to one as this bitset. CHAPTER 5 COLLECTING OBJECTS • boolean get(int bitIndex) returns the value of this bitset’s bit, as a Boolean true/false value (true for 1, false for 0) at the zero-based bitIndex. This method throws IndexOutOfBoundsException when bitIndex is less than 0. • int length() returns the “logical size” of this bitset, which is the index of the highest 1 bit plus 1, or 0 when this bitset contains no 1 bits. • void or(BitSet bs) bitwise inclusive ORs this bitset with bs. This bitset is modified such that a bit is set to 1 when it or the bit at the same position in bs is 1, or when both bits are 1. • void set(int bitIndex, boolean value) sets the bit at the zero-based bitIndex to value (true is converted to 1; false is converted to 0). This method throws IndexOutOfBoundsException when bitIndex is less than 0. • int size() returns the number of bits that are being used by this bitset to represent bit values. • String toString() returns a string representation of this bitset in terms of the positions of bits that are 1; for example, {4, 5, 9, 10}. • void xor(BitSet set) bitwise exclusive ORs this bitset with bs. This bitset is modified such that a bit is set to 1 when either it or the bit at the same position in bs (but not both) is 1. Listing 5-27 presents an application that demonstrates some of these methods, and gives you more insight into how the bitwise AND (&), bitwise inclusive OR (|), and bitwise exclusive OR (^) operators work. Listing 5-27. Working with variable-length bitsets import java.util.BitSet; class BitSetDemo { public static void main(String[] args) { BitSet bs1 = new BitSet(); bs1.set(4, true); bs1.set(5, true); bs1.set(9, true); bs1.set(10, true); BitSet bsTemp = (BitSet) bs1.clone(); dumpBitset(" ", bs1); BitSet bs2 = new BitSet(); bs2.set(4, true); bs2.set(6, true); bs2.set(7, true); bs2.set(9, true); dumpBitset(" ", bs2); bs1.and(bs2); dumpSeparator(Math.min(bs1.size(), 16)); dumpBitset("AND (&) ", bs1); 393 CHAPTER 5 COLLECTING OBJECTS System.out.println(); bs1 = bsTemp; dumpBitset(" ", bs1); dumpBitset(" ", bs2); bsTemp = (BitSet) bs1.clone(); bs1.or(bs2); dumpSeparator(Math.min(bs1.size(), 16)); dumpBitset("OR (|) ", bs1); System.out.println(); bs1 = bsTemp; dumpBitset(" ", bs1); dumpBitset(" ", bs2); bsTemp = (BitSet) bs1.clone(); bs1.xor(bs2); dumpSeparator(Math.min(bs1.size(), 16)); dumpBitset("XOR (^) ", bs1); } static void dumpBitset(String preamble, BitSet bs) { System.out.print(preamble); int size = Math.min(bs.size(), 16); for (int i = 0; i < size; i++) System.out.print(bs.get(i) ? "1" : "0"); System.out.print(" size("+bs.size()+"), length("+bs.length()+")"); System.out.println(); } static void dumpSeparator(int len) { System.out.print(" "); for (int i = 0; i < len; i++) System.out.print("-"); System.out.println(); } } Why did I specify Math.min(bs.size(), 16) in dumpBitset(), and pass a similar expression to dumpSeparator()? I wanted to display exactly 16 bits and 16 dashes (for aesthetics), and needed to account for a bitset’s size being less than 16. Although this does not happen with the JDK’s BitSet class, it might happen with a non-JDK variant. When you run this application, it generates the following output: 0000110001100000 0000101101000000 ---------------AND (&) 0000100001000000 size(64), length(11) size(64), length(10) 0000110001100000 0000101101000000 ---------------0000111101100000 size(64), length(11) size(64), length(10) 0000110001100000 size(64), length(11) OR (|) 394 size(64), length(10) size(64), length(11) CHAPTER 5 COLLECTING OBJECTS 0000101101000000 ---------------XOR (^) 0000011100100000 size(64), length(10) size(64), length(11) ■ Caution Unlike Vector and Hashtable, BitSet is not synchronized. You must externally synchronize access to this class when using BitSet in a multithreaded context. The Collections Framework has made Vector, Stack, Dictionary, and Hashtable obsolete. These types continue to be part of the standard class library to support legacy code. The framework’s Iterator interface has largely obsoleted the Enumeration interface. However, because the java.util.StringTokenizer class (which is somewhat useful, and which is briefly discussed in Chapter 6) uses Enumeration, this interface still has some credibility. The Preferences API (see Appendix C) has made Properties largely obsolete. However, the standard class library still uses Properties in various places (such as in the context of XSLT, discussed in Chapter 10). You’ll probably have a few uses for this class as well. Because BitSet is still relevant, this class continues to be improved. For example, Java 7 introduces new valueOf() class methods (such as static BitSet valueOf(byte[] bytes)) and instance methods (such as int previousSetBit(int fromIndex)) into this class. ■ Note It is not surprising that BitSet is still being improved (as recently as Java 7 at time of writing) when you realize the usefulness of variable-length bitsets. Because of their compactness and other advantages, variablelength bitsets are often used to implement an operating system’s priority queues and facilitate memory page allocation. Unix-oriented file systems also use bitsets to facilitate the allocation of inodes (information nodes) and disk sectors. And bitsets are useful in Huffman coding, a data-compression algorithm for achieving lossless data compression. Creating Your Own Collections Arrays, the Collections Framework, and legacy classes such as BitSet are suitable for organizing groups of objects (or, in the case of BitSet, sets of bits that are interpreted as Boolean true/false values), and you should use them wherever possible before creating your own collection APIs. After all, why “reinvent the wheel?” The Collections Framework supports lists, sets, queues, deques, and maps. If your collection requirement can fit into one of these categories, then go with this framework. Keep in mind that you can also take advantage of trees in TreeSet and TreeMap implementation contexts, and stacks in deque contexts. Perhaps you need a different implementation of one of the Collections Framework core interfaces. If so, you can extend this framework by implementing the interface, or by subclassing one of the more convenient “Abstract” classes, such as AbstractQueue. For example, author Cay Horstmann demonstrates extending this class to implement a circular array queue (see 395 CHAPTER 5 COLLECTING OBJECTS http://www.java2s.com/Code/Java/Collections-DataStructure/Howtoextendthecollectionsframework.htm). OBEYING CONTRACTS When you implement a core interface or extend one of the Abstract classes, you should ensure that your implementation class doesn’t deviate from the various contracts described in the Java documentation for these interfaces. For example, List places the following stipulation on the hashCode() method that it inherits from Collection: The hash code of a list is defined to be the result of the following calculation: int hashCode = 1; for (E e: list) hashCode = 31*hashCode+(e==null ? 0 : e.hashCode()); This calculation ensures that list1.equals(list2) implies that list1.hashCode() == list2.hashCode() for any two lists, list1 and list2, as required by the general contract of Object.hashCode(). The AbstractList class, which partially implements List, has this to say about hashCode():This implementation uses exactly the code that is used to define the list hash function in the documentation for the List.hashCode() method. When it comes to lists, you should also be aware of the RandomAccess interface: ArrayList implements the RandomAccess interface, which is a marker interface used by List implementation classes to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists. The best algorithms for manipulating random access List implementations (such as ArrayList) can produce quadratic behavior when applied to sequential access List implementations (such as LinkedList). Generic list algorithms are encouraged to check whether the given list is an instance of this interface (via instanceof) before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance. The distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear (a line whose distance to a given curve tends to zero) access times if they get huge, but constant access times in practice. Such a List implementation class should generally implement this interface. As a rule of thumb, a List implementation class should implement this interface if, for typical instances of the class, the following loop: for (int i=0, n=list.size(); i < n; i++) list.get(i); runs faster than the following loop: for (Iterator i=list.iterator(); i.hasNext();) i.next(); 396 CHAPTER 5 COLLECTING OBJECTS Keep these advices in mind and you should find it easier to extend the Collections Framework. You might require a collection that isn’t supported by the Collections Framework (or perhaps you only think it isn’t supported). For example, you might want to model a sparse matrix, a table where many or most of its elements are zeros (see http://en.wikipedia.org/wiki/Sparse_matrix). A sparse matrix is a good data structure for implementing a spreadsheet, for example. If the elements represent bits, you could use BitSet to represent the matrix. If the elements are objects, you might use an array. The problem with either approach is scalability and the limits of heap space. For example, suppose you need a table with 100,000 rows and 100,000 columns, yielding a maximum of 10 billion elements. You can forget about using BitSet (assuming that each entry occupies a single bit) because 10,000,000,000 is too large to pass to the BitSet(int nbits) constructor; some information will be lost when you cast this long integer to an integer. You can also forget about using an array because you’ll exhaust the JVM’s memory and obtain a java.lang.OutOfMemoryError at runtime. Because you’re dealing with a sparse matrix, assume that no more than 25,000 table entries are nonzero at any one time. After all, a sparse matrix has a sparse number of nonzero entries. This is a lot more manageable. You won’t use BitSet to represent this matrix because you’ll assume that each matrix entry is an object. You can’t use a two-dimensional array to store these objects because the array would need 100,000 rows by 100,000 columns to properly index the sparse matrix, and you would exhaust memory by being extremely wasteful in storing zero (or null, in the case of object) values. There is another way to represent this matrix, and that is to create a linked list of nodes. A node is an object consisting of value and link fields. Unlike an array, where each element stores a single value of the same primitive type or reference supertype, a node can store multiple values of different types. It can also store links (references to other nodes). Consider Listing 5-28’s Node class: Listing 5-28. A node consists of value fields and link fields class Node { // value field String name; // link field Node next; } Node describes simple nodes where each node consists of a single name value field and a single next link field. Notice that next is of the same type as the class in which it is declared. This arrangement lets a node instance store a reference to another node instance (which is the next node) in this field. The resulting nodes are linked together. Listing 5-29 presents a Nodes class that demonstrates connecting Nodes together into a linked list, and then iterating over this list to output the values of the name fields. Listing 5-29. Creating and iterating over a linked list of nodes class Nodes { public static void main(String[] args) { Node top = new Node(); 397 CHAPTER 5 COLLECTING OBJECTS top.name = "node 1"; top.next = new Node(); top.next.name = "node 2"; top.next.next = new Node(); top.next.next.name = "node 3"; top.next.next.next = null; Node temp = top; while (temp != null) { System.out.println(temp.name); temp = temp.next; } } } Listing 5-29 demonstrates the creation of a singly linked list (a list where each node consists of a single link field). The first Node instance is pointed to by reference variable top, which identifies the top of the list. Each subsequent node in this linked list is referenced from its predecessor’s next field. The final next field is set to null to signify the end of the linked list. (This explicit initialization is unnecessary because the field defaults to the null reference during instance initialization, but is present for clarity). Figure 5-5 reveals this three-node linked list. Figure 5-5. Reference variable top points to the first node in this three-node linked list. Listing 5-29 also shows you how to traverse this singly linked list by following each Node object’s next field. Prior to the traversal, top’s reference is assigned to variable temp, to preserve the start of this linked list so that further manipulations (node insertions, removals, updates) and searches can be performed. The while loop iterates until temp contains the null reference, outputting each node’s name field and assigning the reference in the current node’s next field to temp. When you run this application, it generates the following output: node 1 node 2 node 3 You might declare the following Cell class to represent a sparse matrix node for a spreadsheet, which is known as a cell: class Cell { int row; int col; Object value; Node next; } When called upon to update the spreadsheet on the screen, your spreadsheet application’s rendering code traverses its linked list of Cell nodes. For each cell, it first examines (row, col) to learn if the cell is visible and should be rendered. If the cell is visible, the instanceof operator is used to determine value’s type, and value is then displayed. As soon as null is encountered, the rendering code knows that there are no more spreadsheet elements to render. 398 CHAPTER 5 COLLECTING OBJECTS Before creating your own linked list class to store Cell instances, you should realize that doing so isn’t necessary. Instead, you can leverage the Collection Framework’s LinkedList class to store Cell instances (without the unnecessary next fields). Although you might occasionally need to create your own node-based collections, the moral of this exercise is that you should always think about using arrays, the Collections Framework, or a legacy class such as BitSet before inventing your own API to collect objects. EXERCISES The following exercises are designed to test your understanding of collections: 1. As an example of array list usefulness, create a JavaQuiz application that presents a multiple-choice-based quiz on Java features. The JavaQuiz class’s main() method first populates the array list with the entries in a QuizEntry array (e.g., new QuizEntry("What was Java's original name?", new String[] { "Oak", "Duke", "J", "None of the above" },'A')). Each entry consists of a question, four possible answers, and the letter (A, B, C, or D) of the correct answer. main() then uses the array list’s iterator() method to return an Iterator instance, and this instance’s hasNext() and next() methods to iterate over the list. Each of the iterations outputs the question and four possible answers, and then prompts the user to enter the correct choice. After the user enters A, B, C, or D (via System.in.read()), main() outputs a message stating whether or not the user made the correct choice. 2. Create a word-counting application (WC) that reads words from the standard input (via System.in.read()) and stores them in a map along with their frequency counts. For this exercise, a word consists of letters only; use the java.lang.Character class’s isLetter() method to make this determination. Also, use Map’s get() and put() methods and take advantage of autoboxing to record a new entry or update an existing entry’s count—the first time a word is seen, its count is set to 1. Use Map’s entrySet() method to return a Set of entries, and iterate over these entries, outputting each entry to the standard output. 3. Collections provides the static int frequency(Collection<?> c, Object o) method to return the number of collection c elements that are equal to o. Create a FrequencyDemo application that reads its command-line arguments and stores all arguments except for the last argument in a list, and then calls frequency() with the list and last command-line argument as this method’s arguments. It then outputs this method’s return value (the number of occurrences of the last command-line argument in the previous command-line arguments). For example, java FrequencyDemo should output Number of occurrences of null = 0, and java FrequencyDemo how much wood could a woodchuck chuck if a woodchuck could chuck wood wood should output Number of occurrences of wood = 2. 399 CHAPTER 5 COLLECTING OBJECTS Summary This book was purchased by [email protected] The Collections Framework is a standard architecture for representing and manipulating collections, which are groups of objects stored in instances of classes designed for this purpose. This framework largely consists of core interfaces, implementation classes, and utility classes. The core interfaces make it possible to manipulate collections independently of their implementations. They include Iterable, Collection, List, Set, SortedSet, NavigableSet, Queue, Deque, Map, SortedMap, and NavigableMap. Collection extends Iterable; List, Set, and Queue each extend Collection; SortedSet extends Set; NavigableSet extends SortedSet; Deque extends Queue; SortedMap extends Map; and NavigableMap extends SortedMap. The framework’s implementation classes include ArrayList, LinkedList, TreeSet, HashSet, LinkedHashSet, EnumSet, PriorityQueue, ArrayDeque, TreeMap, HashMap, LinkedHashMap, IdentityHashMap, WeakHashMap, and EnumMap. The name of each concrete class ends in a core interface name, identifying the core interface on which it is based. The framework’s implementation classes also include the abstract AbstractCollection, AbstractList, AbstractSequentialList, AbstractSet, AbstractQueue, and AbstractMap classes. These classes offer skeletal implementations of the core interfaces to facilitate the creation of concrete implementation classes. The Collections Framework would not be complete without its Arrays and Collections utility classes. Each class supplies various class methods that implement useful algorithms in the contexts of arrays and collections. Before Java 1.2’s introduction of the Collections Framework, developers had two choices where collections were concerned: create their own frameworks, or use the Vector, Enumeration, Stack, Dictionary, Hashtable, Properties, and BitSet types, which were introduced by Java 1.0. The Collections Framework has made Vector, Stack, Dictionary, and Hashtable obsolete. The framework’s Iterator interface has largely obsoleted the Enumeration interface. The Preferences API has made Properties largely obsolete. Because BitSet is still relevant, this class continues to be improved. Arrays, the Collections Framework, and legacy classes such as BitSet are suitable for organizing groups of objects (or, in the case of BitSet, sets of bits that are interpreted as Boolean true/false values), and you should use them wherever possible before creating your own collection APIs. However, you might need a different implementation of one of the Collections Framework core interfaces. If so, you can extend this framework by implementing the interface, or by subclassing one of the more convenient “Abstract” classes, such as AbstractQueue. You might require a collection that isn’t supported by the Collections Framework (or perhaps you only think it isn’t supported). For example, you might want to model a sparse matrix, a table where many or most of its elements are zeros. A sparse matrix is a good data structure for implementing a spreadsheet, for example. To model a spreadsheet or other sparse matrix, you can work with nodes, which are objects consisting of value and link fields. Unlike an array, where each element stores a single value of the same primitive type or reference supertype, a node can store multiple values of different types. It can also store references to other nodes, which are known as links. You can connect nodes together into linked lists, but (at least for singly linked lists) there is no need to do so because you can take advantage of the Collections Framework’s LinkedList class for this task. After all, you should not “reinvent the wheel.” Broadly speaking, the Collections Framework is an example of a utility API. Chapter 6 continues to focus on utility APIs by introducing you to Java’s concurrency utilities, which extend the Collections Framework, the java.util.Objects class, and more. 400 CHAPTER 6 Touring Additional Utility APIs Chapter 5 introduced you to the Collections Framework, which is a collection of utility APIs. Chapter 6 introduces you to additional utility APIs, specifically the concurrency utilities, Objects, and Random. Concurrency Utilities Java 5 introduced the concurrency utilities, which are classes and interfaces that simplify the development of concurrent (multithreaded) applications. These types are located in the java.util.concurrent package and in its java.util.concurrent.atomic and java.util.concurrent.locks subpackages. The concurrency utilities leverage the low-level Threading API (see Chapter 4) in their implementations and provide higher-level building blocks to simplify creating multithreaded applications. They are organized into executor, synchronizer, concurrent collection, lock, atomic variable, and additional utility categories. Executors Chapter 4 introduced the Threading API, which lets you execute runnable tasks via expressions such as new Thread(new RunnableTask()).start();. These expressions tightly couple task submission with the task’s execution mechanics (run on the current thread, a new thread, or a thread arbitrarily chosen from a pool [group] of threads). Note A task is an object whose class implements the java.lang.Runnable interface (a runnable task) or the java.util.concurrent.Callable interface (a callable task). The concurrency utilities provide executors as a high-level alternative to low-level Threading API expressions for executing runnable tasks. An executor is an object whose class directly or indirectly implements the java.util.concurrent.Executor interface, which decouples task submission from taskexecution mechanics. 401 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Note The executor framework’s use of interfaces to decouple task submission from task-execution mechanics is analogous to the Collections Framework’s use of core interfaces to decouple lists, sets, queues, deques, and maps from their implementations. Decoupling results in flexible code that is easier to maintain. Executor declares a solitary void execute(Runnable runnable) method that executes the runnable task named runnable at some point in the future. execute() throws java.lang.NullPointerException when runnable is null, and java.util.concurrent.RejectedExecutionException when it cannot execute runnable. Note RejectedExecutionException can be thrown when an executor is shutting down and does not want to accept new tasks. Also, this exception can be thrown when the executor does not have enough room to store the task (perhaps the executor uses a bounded blocking queue to store tasks and the queue is full—I discuss blocking queues later in this chapter). The following example presents the Executor equivalent of the aforementioned new Thread(new RunnableTask()).start(); expression: Executor executor = ...; // ... represents some executor creation executor.execute(new RunnableTask()); Although Executor is easy to use, this interface is limited in various ways: • Executor focuses exclusively on Runnable. Because Runnable’s run() method does not return a value, there is no convenient way for a runnable task to return a value to its caller. • Executor does not provide a way to track the progress of executing runnable tasks, cancel an executing runnable task, or determine when the runnable task finishes execution. • Executor cannot execute a collection of runnable tasks. • Executor does not provide a way for an application to shut down an executor (much less to properly shut down an executor). These limitations are addressed by the java.util.concurrent.ExecutorService interface, which extends Executor, and whose implementation is typically a thread pool (a group of reusable threads). Table 6-1 describes ExecutorService’s methods. 402 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Table 6-1. ExecutorService Methods Method Description boolean awaitTermination(long timeout, TimeUnit unit) Block (wait) until all tasks have finished after a shutdown request, the timeout (measured in unit time units) expires, or the current thread is interrupted, whichever happens first. Return true when this executor has terminated, and false when the timeout elapses before termination. This method throws java.lang.InterruptedException when interrupted. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) Execute each callable task in the tasks collection, and return a java.util.List of java.util.concurrent.Future instances that hold task statuses and results when all tasks complete— a task completes through normal termination or by throwing an exception. The List of Futures is in the same sequential order as the sequence of tasks returned by tasks’ iterator. This method throws InterruptedException when it is interrupted while waiting, in which case unfinished tasks are canceled, NullPointerException when tasks or any of its elements is null, and RejectedExecutionException when any one of tasks’ tasks cannot be scheduled for execution. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) Execute each callable task in the tasks collection, and return a List of Future instances that hold task statuses and results when all tasks complete—a task completes through normal termination or by throwing an exception—or the timeout (measured in unit time units) expires. Tasks that are not completed at expiry are canceled. The List of Futures is in the same sequential order as the sequence of tasks returned by tasks’ iterator. This method throws InterruptedException when it is interrupted while waiting, in which case unfinished tasks are canceled. It also throws NullPointerException when tasks, any of its elements, or unit is null; and throws RejectedExecutionException when any one of tasks’ tasks cannot be scheduled for execution. <T> T invokeAny(Collection<? extends Callable<T>> tasks) Execute the given tasks, returning the result of an arbitrary task that has completed successfully (i.e., without throwing an exception), if any does. Upon normal or exceptional return, tasks that have not completed are canceled. This method throws InterruptedException when it is interrupted while waiting, NullPointerException when tasks or any of its elements is null, java.lang.IllegalArgumentException when tasks is empty, java.util.concurrent.ExecutionException when no task completes successfully, and RejectedExecutionException when none of the tasks can be scheduled for execution. 403 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS 404 <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) Execute the given tasks, returning the result of an arbitrary task that has completed successfully (i.e., without throwing an exception), if any does before the timeout (measured in unit time units) expires—tasks that are not completed at expiry are canceled. Upon normal or exceptional return, tasks that have not completed are canceled. This method throws InterruptedException when it is interrupted while waiting, NullPointerException when tasks, any of its elements, or unit is null, IllegalArgumentException when tasks is empty, java.util.concurrent.TimeoutException when the timeout elapses before any task successfully completes, ExecutionException when no task completes successfully, and RejectedExecutionException when none of the tasks can be scheduled for execution. boolean isShutdown() Return true when this executor has been shut down; otherwise, return false. boolean isTerminated() Return true when all tasks have completed following shutdown; otherwise, return false. This method will never return true prior to shutdown() or shutdownNow() being called. void shutdown() Initiate an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Calling this method has no effect after the executor has shut down. This method does not wait for previously submitted tasks to complete execution. Use awaitTermination() if waiting is necessary. List<Runnable> shutdownNow() Attempt to stop all actively executing tasks, halt the processing of waiting tasks, and return a list of the tasks that were awaiting execution. There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate. This method does not wait for actively executing tasks to terminate. Use awaitTermination() if waiting is necessary. <T> Future<T> submit(Callable<T> task) Submit a callable task for execution and return a Future instance representing task’s pending results. The Future instance’s get() method returns task’s result upon successful completion. This method throws RejectedExecutionException when task cannot be scheduled for execution, and NullPointerException when task is null. If you would like to immediately block while waiting for a task to complete, you can use constructions of CHAPTER 6 TOURING ADDITIONAL UTILITY APIS the form result = exec.submit(aCallable).get();. Future<?> submit(Runnable task) Submit a runnable task for execution and return a Future instance representing task’s pending results. The Future instance’s get() method returns task’s result upon successful completion. This method throws RejectedExecutionException when task cannot be scheduled for execution, and NullPointerException when task is null. <T> Future<T> submit(Runnable task, T result) Submit a runnable task for execution and return a Future instance whose get() method returns result upon successful completion. This method throws RejectedExecutionException when task cannot be scheduled for execution, and NullPointerException when task is null. Table 6-1 refers to java.util.concurrent.TimeUnit, an enum that represents time durations at given units of granularity: DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, and SECONDS. Furthermore, TimeUnit declares methods for converting across units (e.g., long toHours(long duration)), and for performing timing and delay operations (e.g., void sleep(long timeout)) in these units. Table 6-1 also refers to callable tasks, which are analogous to runnable tasks. Unlike Runnable, whose void run() method cannot throw checked exceptions, Callable<V> declares a V call() method that returns a value, and which can throw checked exceptions because call() is declared with a throws Exception clause. Finally, Table 6-1 refers to the Future interface, which represents the result of an asynchronous computation. Future, whose generic type is Future<V>, provides methods for canceling a task, for returning a task’s value, and for determining whether or not the task has finished. Table 6-2 describes Future’s methods. Table 6-2. Future Methods Method Description boolean cancel(boolean mayInterruptIfRunning) Attempt to cancel execution of this task, and return true when the task was canceled; otherwise, return false (perhaps the task completed normally before this method was called). The cancellation attempt fails when the task has completed, has already been canceled, or could not be canceled for some other reason. If successful and this task had not started when cancel() was called, the task should never run. If the task has already started, then mayInterruptIfRunning determines whether (true) or not (false) the thread executing this task should be interrupted in an attempt to stop the task. After this method returns, subsequent calls to isDone() always return true. Subsequent calls to isCancelled() always return true when cancel() returns true. 405 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS V get() Wait if necessary for the task to complete and return the result. This method throws java.util.concurrent.CancellationException when the task was canceled prior to this method being called, ExecutionException when the task threw an exception, and InterruptedException when the current thread was interrupted while waiting. V get(long timeout, TimeUnit unit) Wait at most timeout units (as specified by unit) for the task to complete and then return the result (if available). This method throws CancellationException when the task was canceled prior to this method being called, ExecutionException when the task threw an exception, InterruptedException when the current thread was interrupted while waiting, and TimeoutException when this method’s timeout value expires (the wait times out). boolean isCancelled() Return true when this task was canceled before it completed normally; otherwise, return false. boolean isDone() Return true when this task completed; otherwise, return false. Completion may be due to normal termination, an exception, or cancellation—this method returns true in all these cases. Suppose you intend to write an application whose graphical user interface (GUI) lets the user enter a word. After the user enters the word, the application presents this word to several online dictionaries and obtains each dictionary’s entry. These entries are subsequently displayed to the user. Because online access can be slow, and because the user interface should remain responsive (perhaps the user might want to end the application), you offload the “obtain word entries” task to an executor that runs this task on a separate thread. The following example employs ExecutorService, Callable, and Future to accomplish this objective: ExecutorService executor = ...; // ... represents some executor creation Future<String[]> taskFuture = executor.submit(new Callable<String[]>() { public String[] call() { String[] entries = ...; // Access online dictionaries // with search word and populate // entries with their resulting // entries. return entries; } }); // Do stuff. String entries = taskFuture.get(); 406 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS After obtaining an executor in some manner (you will learn how shortly), the example’s main thread submits a callable task to the executor. The submit() method immediately returns with a reference to a Future object for controlling task execution and accessing results. The main thread ultimately calls this object’s get() method to get these results. Note The java.util.concurrent.ScheduledExecutorService interface extends ExecutorService and describes an executor that lets you schedule tasks to run once or to execute periodically after a given delay. Although you could create your own Executor, ExecutorService, and ScheduledExecutorService implementations (such as class DirectExecutor implements Executor { public void execute(Runnable r) { r.run(); } }—run executor directly on the calling thread), the concurrency utilities offer a simpler alternative: java.util.concurrent.Executors. Tip If you intend to create your own ExecutorService implementations, you will find it helpful to work with the java.util.concurrent.AbstractExecutorService and java.util.concurrent.FutureTask classes. The Executors utility class declares several class methods that return instances of various ExecutorService and ScheduledExecutorService implementations (and other kinds of instances). This class’s static methods accomplish the following tasks: • Create and return an ExecutorService instance that is configured with commonly used configuration settings. • Create and return a ScheduledExecutorService instance that is configured with commonly used configuration settings. • Create and return a “wrapped” ExecutorService or ScheduledExecutorService instance that disables reconfiguration of the executor service by making implementation-specific methods inaccessible. • Create and return a java.util.concurrent.ThreadFactory instance for creating new threads. • Create and return a Callable instance out of other closure-like forms so that it can be used in execution methods requiring Callable arguments (e.g., ExecutorService’s submit(Callable) method). (Check out Wikipedia’s “Closure (computer science)” entry [http://en.wikipedia.org/wiki/Closure_(computer_science)] to learn about closures.) For example, static ExecutorService newFixedThreadPool(int nThreads) creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At most, nThreads threads are actively processing tasks. If additional tasks are submitted when all threads are active, they wait in the queue for an available thread. 407 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS If any thread terminates because of a failure during execution before the executor shuts down, a new thread will take its place when needed to execute subsequent tasks. The threads in the pool will exist until the executor is explicitly shut down. This method throws IllegalArgumentException when you pass zero or a negative value to nThreads. Note Threads pools are used to eliminate the overhead from having to create a new thread for each submitted task. Thread creation is not cheap, and having to create many threads could severely impact an application’s performance. You would commonly use executors, runnables, callables, and futures in an input/output context. (I discuss Java’s support for filesystem input/output in Chapter 8.) Performing a lengthy calculation offers another scenario where you could use these types. For example, Listing 6-1 uses an executor, a callable, and a future in a calculation context of Euler’s number e (2.71828…). Listing 6-1. Calculating Euler’s number e import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import import import import import java.util.concurrent.Callable; java.util.concurrent.ExecutionException; java.util.concurrent.ExecutorService; java.util.concurrent.Executors; java.util.concurrent.Future; class CalculateE { final static int LASTITER = 17; public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(1); Callable<BigDecimal> callable; callable = new Callable<BigDecimal>() { public BigDecimal call() { MathContext mc = new MathContext(100, RoundingMode.HALF_UP); BigDecimal result = BigDecimal.ZERO; for (int i = 0; i <= LASTITER; i++) { BigDecimal factorial = factorial(new BigDecimal(i)); BigDecimal res = BigDecimal.ONE.divide(factorial, mc); result = result.add(res); } return result; 408 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS } public BigDecimal factorial(BigDecimal n) { if (n.equals(BigDecimal.ZERO)) return BigDecimal.ONE; else return n.multiply(factorial(n.subtract(BigDecimal.ONE))); } }; Future<BigDecimal> taskFuture = executor.submit(callable); try { while (!taskFuture.isDone()) System.out.println("waiting"); System.out.println(taskFuture.get()); } catch(ExecutionException ee) { System.err.println("task threw an exception"); System.err.println(ee); } catch(InterruptedException ie) { System.err.println("interrupted while waiting"); } executor.shutdownNow(); } } The main thread that executes Listing 6-1’s main() method first obtains an executor by calling Executors’ newFixedThreadPool() method. It then instantiates an anonymous class that implements Callable and submits this task to the executor, receiving a Future instance in response. After submitting a task, a thread typically does some other work until it needs to obtain the task’s result. I have chosen to simulate this work by having the main thread repeatedly output a waiting message until the Future instance’s isDone() method returns true. (In a realistic application, I would avoid this looping.) At this point, the main thread calls the instance’s get() method to obtain the result, which is then output. Caution It is important to shut down the executor after it completes; otherwise, the application might not end. The application accomplishes this task by calling shutdownNow(). The callable’s call() method calculates e by evaluating the mathematical power series e = 1/0!+1/1!+1/2!+…. This series can be evaluated by summing 1/n!, where n ranges from 0 to infinity. call() first instantiates java.math.MathContext to encapsulate a precision (number of digits) and a rounding mode. I chose 100 as an upper limit on e’s precision and HALF_UP as the rounding mode. 409 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Tip Increase the precision as well as LASTITER’s value to converge the series to a lengthier and more accurate approximation of e. call() next initializes a java.math.BigDecimal local variable named result to BigDecimal.ZERO. It then enters a loop that calculates a factorial, divides BigDecimal.ONE by the factorial, and adds the division result to result. The divide() method takes the MathContext instance as its second argument to ensure that the division does not result in a nonterminating decimal expansion (the quotient result of the division cannot be represented exactly—0.3333333…, for example), which throws java.lang.ArithmeticException (to alert the caller to the fact that the quotient cannot be represented exactly), which the executor rethrows as ExecutionException. When you run this application, you should observe output similar to the following: This book was purchased by [email protected] waiting waiting waiting waiting 2.71828182845904507051604779584860506117897963525103269890073500406522504250484331405588797434 4245741730039454062711 Synchronizers The Threading API offers synchronization primitives for synchronizing thread access to critical sections. Because it can be difficult to correctly write synchronized code that is based on these primitives, the concurrency utilities include synchronizers, classes that facilitate common forms of synchronization. Five commonly used synchronizers are countdown latches, cyclic barriers, exchangers, phasers, and semaphores: 410 • A countdown latch lets one or more threads wait at a “gate” until another thread opens this gate, at which point these other threads can continue. The java.util.concurrent.CountDownLatch class implements this synchronizer. • A cyclic barrier lets a group of threads wait for each other to reach a common barrier point. The java.util.concurrent.CyclicBarrier class implements this synchronizer, and makes use of the java.util.concurrent.BrokenBarrierException class. CyclicBarrier instances are useful in applications involving fixed sized parties of threads that must occasionally wait for each other. CyclicBarrier supports an optional Runnable, known as a barrier action, which runs once per barrier point after the last thread in the party arrives but before any threads are released. This barrier action is useful for updating shared state before any of the parties continue. • An exchanger lets a pair of threads exchange objects at a synchronization point. The java.util.concurrent.Exchanger class implements this synchronizer. Each thread presents some object on entry to Exchanger’s exchange() method, matches with a partner thread, and receives its partner’s object on return. Exchangers may be useful in applications such as genetic algorithms (see http://en.wikipedia.org/wiki/Genetic_algorithm) and pipeline designs. CHAPTER 6 TOURING ADDITIONAL UTILITY APIS • A phaser is a reusable synchronization barrier that is similar in functionality to CyclicBarrier and CountDownLatch, but offers more flexibility. For example, unlike with other barriers, the number of threads that register to synchronize on a phaser may vary over time. The java.util.concurrent.Phaser class implements this synchronizer. Phaser may be used instead of a CountDownLatch to control a oneshot action that serves a variable number of parties. It may also be used by tasks executing in the context of the Fork/Join Framework, discussed later in this chapter. • A semaphore maintains a set of permits for restricting the number of threads that can access a limited resource. The java.util.concurrent.Semaphore class implements this synchronizer. Each call to one of Semaphore’s acquire() methods blocks if necessary until a permit is available, and then takes it. Each call to release() adds a permit, potentially releasing a blocking acquirer. However, no actual permit objects are used; the Semaphore instance only keeps a count of the number of available permits and acts accordingly. Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource. Consider the CountDownLatch class. Each of its instances is initialized to a nonzero count. A thread calls one of CountDownLatch’s await() methods to block until the count reaches zero. Another thread calls CountDownLatch’s countDown() method to decrement the count. Once the count reaches zero, the waiting threads are allowed to continue. Note After waiting threads are released, subsequent calls to await() return immediately. Also, because the count cannot be reset, a CountDownLatch instance can be used only once. When repeated use is a requirement, use the CyclicBarrier class instead. We can use CountDownLatch to ensure that worker threads start working at approximately the same time. For example, check out Listing 6-2. Listing 6-2. Using a countdown latch to trigger a coordinated start import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class CountDownLatchDemo { final static int NTHREADS = 3; public static void main(String[] args) { final CountDownLatch startSignal = new CountDownLatch(1); final CountDownLatch doneSignal = new CountDownLatch(NTHREADS); Runnable r = new Runnable() { public void run() { 411 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS try { report("entered run()"); startSignal.await(); // wait until told to proceed report("doing work"); Thread.sleep((int)(Math.random()*1000)); doneSignal.countDown(); // reduce count on which // main thread is waiting } catch (InterruptedException ie) { System.err.println(ie); } } void report(String s) { System.out.println(System.currentTimeMillis()+": "+ Thread.currentThread()+": "+s); } }; ExecutorService executor = Executors.newFixedThreadPool(NTHREADS); for (int i = 0; i < NTHREADS; i++) executor.execute(r); try { System.out.println("main thread doing something"); Thread.sleep(1000); // sleep for 1 second startSignal.countDown(); // let all threads proceed System.out.println("main thread doing something else"); doneSignal.await(); // wait for all threads to finish executor.shutdownNow(); } catch (InterruptedException ie) { System.err.println(ie); } } } Listing 6-2’s main thread first creates a pair of countdown latches. The startSignal countdown latch prevents any worker thread from proceeding until the main thread is ready for them to proceed. The doneSignal countdown latch causes the main thread to wait until all worker threads have finished. The main thread next creates a runnable whose run() method is executed by subsequently created worker threads. The run() method first outputs an initial message and then calls startSignal’s await() method to wait for this countdown latch’s count to read zero before it can proceed. Once this happens, run() outputs a message that indicates work is being done, and sleeps for a random period of time (0 through 999 milliseconds) to simulate this work. At this point, run() invokes doneSignal’s countDown() method to decrement this latch’s count. Once this count reaches zero, the main thread waiting on this signal will continue, shutting down the executor and terminating the application. 412 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS After creating the runnable, the main thread obtains an executor that’s based on a thread pool of NTHREADS threads, and then calls the executor’s execute() method NTHREADS times, passing the runnable to each of the NTHREADS pool-based threads. This action starts the worker threads, which enter run(). Next, the main thread outputs a message and sleeps for one second to simulate doing additional work (giving all the worker threads a chance to have entered run() and invoke startSignal.await()), invokes startSignal’s countdown() method to cause the worker threads to start running, outputs a message to indicate that it is doing something else, and invokes doneSignal’s await() method to wait for this countdown latch’s count to reach zero before it can proceed. When you run this application, you will observe output similar to the following: main thread doing something 1312936533890: Thread[pool-1-thread-1,5,main]: 1312936533890: Thread[pool-1-thread-2,5,main]: 1312936533890: Thread[pool-1-thread-3,5,main]: 1312936534890: Thread[pool-1-thread-1,5,main]: 1312936534890: Thread[pool-1-thread-2,5,main]: 1312936534890: Thread[pool-1-thread-3,5,main]: main thread doing something else entered run() entered run() entered run() doing work doing work doing work You might observe the main thread doing something else message appearing between the last “entered run()” message and the first “doing work” message. Note For brevity, I have avoided examples that demonstrate CyclicBarrier, Exchanger, Phaser, and Semaphore. Instead, I refer you to the Java documentation for these classes. Each class’s documentation provides an example that shows you how to use the class. Concurrent Collections The java.util.concurrent package includes several interfaces and classes that are concurrency-oriented extensions to the Collections Framework (see Chapter 5): • BlockingDeque is a subinterface of BlockingQueue and java.util.Deque that also supports blocking operations that wait for the deque to become nonempty before retrieving an element, and wait for space to become available in the deque before storing an element. The LinkedBlockingDeque class implements this interface. • BlockingQueue is a subinterface of java.util.Queue that also supports blocking operations that wait for the queue to become nonempty before retrieving an element, and wait for space to become available in the queue before storing an element. Each of the ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, and SynchronousQueue classes implements this interface. • ConcurrentMap is a subinterface of java.util.Map that declares additional atomic putIfAbsent(), remove(), and replace() methods. The ConcurrentHashMap class (the concurrent equivalent of java.util.HashMap) and the ConcurrentSkipListMap class implement this interface. 413 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS • ConcurrentNavigableMap is a subinterface of ConcurrentMap and java.util.NavigableMap. The ConcurrentSkipListMap class implements this interface. • TransferQueue is a subinterface of BlockingQueue and describes a blocking queue in which producers may wait for consumers to receive elements. The LinkedTransferQueue class implements this interface. • ConcurrentLinkedDeque is an unbounded concurrent deque based on linked nodes. • ConcurrentLinkedQueue is an unbounded thread-safe FIFO implementation of the Queue interface. • ConcurrentSkipListSet is a scalable concurrent NavigableSet implementation. • CopyOnWriteArrayList is a thread-safe variant of java.util.ArrayList in which all mutative (nonimmutable) operations (add, set, and so on) are implemented by making a fresh copy of the underlying array. • CopyOnWriteArraySet is a java.util.Set implementation that uses an internal CopyOnWriteArrayList instance for all its operations. Listing 6-3 uses BlockingQueue and ArrayBlockingQueue in an alternative to Listing 4-27’s producerconsumer application (PC). Listing 6-3. The blocking queue equivalent of Listing 4-27’s PC application import import import import java.util.concurrent.ArrayBlockingQueue; java.util.concurrent.BlockingQueue; java.util.concurrent.ExecutorService; java.util.concurrent.Executors; class PC { public static void main(String[] args) { final BlockingQueue<Character> bq; bq = new ArrayBlockingQueue<Character>(26); final ExecutorService executor = Executors.newFixedThreadPool(2); Runnable producer; producer = new Runnable() { public void run() { for (char ch = 'A'; ch <= 'Z'; ch++) { try { bq.put(ch); System.out.println(ch+" produced by producer."); } catch (InterruptedException ie) 414 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS { assert false; } } } }; executor.execute(producer); Runnable consumer; consumer = new Runnable() { public void run() { char ch = '\0'; do { try { ch = bq.take(); System.out.println(ch+" consumed by consumer."); } catch (InterruptedException ie) { assert false; } } while (ch != 'Z'); executor.shutdownNow(); } }; executor.execute(consumer); } } Listing 6-3 uses BlockingQueue’s put() and take() methods, respectively, to put an object on the blocking queue and to remove an object from the blocking queue. put() blocks when there is no room to put an object; take() blocks when the queue is empty. Although BlockingQueue ensures that a character is never consumed before it is produced, this application’s output may indicate otherwise. For example, here is a portion of the output from one run: Y Y Z Z consumed produced consumed produced by by by by consumer. producer. consumer. producer. Chapter 4’s PC application overcame this incorrect output order by introducing an extra layer of synchronization around setSharedChar()/System.out.println() and an extra layer of synchronization around getSharedChar()/System.out.println(). The next section shows you an alternative in the form of locks. 415 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Locks The java.util.concurrent.locks package provides interfaces and classes for locking and waiting for conditions in a manner that is distinct from built-in synchronization and monitors. This package’s most basic lock interface is Lock, which provides more extensive locking operations than can be achieved via the synchronized reserved word. Lock also supports a wait/notification mechanism through associated Condition objects. Note The biggest advantage of Lock objects over the implicit locks that are obtained when threads enter critical sections (controlled via the synchronized reserved word) is their ability to back out of an attempt to acquire a lock. For example, the tryLock() method backs out when the lock is not available immediately or when a timeout expires (if specified). Also, the lockInterruptibly() method backs out when another thread sends an interrupt before the lock is acquired. ReentrantLock implements Lock, describing a reentrant mutual exclusion Lock implementation with the same basic behavior and semantics as the implicit monitor lock accessed via synchronized, but with extended capabilities. Listing 6-4 demonstrates Lock and ReentrantLock in a version of Listing 6-3 that ensures that the output is never shown in incorrect order (a consumed message appearing before a produced message). Listing 6-4. Achieving synchronization in terms of locks import import import import java.util.concurrent.ArrayBlockingQueue; java.util.concurrent.BlockingQueue; java.util.concurrent.ExecutorService; java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class PC { public static void main(String[] args) { final Lock lock = new ReentrantLock(); final BlockingQueue<Character> bq; bq = new ArrayBlockingQueue<Character>(26); final ExecutorService executor = Executors.newFixedThreadPool(2); Runnable producer; producer = new Runnable() { public void run() { for (char ch = 'A'; ch <= 'Z'; ch++) { try 416 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS { lock.lock(); try { while (!bq.offer(ch)) { lock.unlock(); Thread.sleep(50); lock.lock(); } System.out.println(ch+" produced by producer."); } catch (InterruptedException ie) { assert false; } } finally { lock.unlock(); } } } }; executor.execute(producer); Runnable consumer; consumer = new Runnable() { public void run() { char ch = '\0'; do { try { lock.lock(); try { Character c; while ((c = bq.poll()) == null) { lock.unlock(); Thread.sleep(50); lock.lock(); } ch = c; // unboxing behind the scenes System.out.println(ch+" consumed by consumer."); } catch (InterruptedException ie) { assert false; } 417 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS } finally { lock.unlock(); } } while (ch != 'Z'); executor.shutdownNow(); } }; executor.execute(consumer); } } Listing 6-4 uses Lock’s lock() and unlock() methods to obtain and release a lock. When a thread calls lock() and the lock is unavailable, the thread is disabled (and cannot be scheduled) until the lock becomes available. This listing also uses BlockingQueue’s offer() method instead of put() to store an object in the blocking queue, and its poll() method instead of take() to retrieve an object from the queue. These alternative methods are used because they do not block. If I had used put() and take(), this application would have deadlocked in the following scenario: 1. The consumer thread acquires the lock via its lock.lock() call. 2. The producer thread attempts to acquire the lock via its lock.lock() call and is disabled because the consumer thread has already acquired the lock. 3. The consumer thread calls take() to obtain the next java.lang.Character object from the queue. 4. Because the queue is empty, the consumer thread must wait. 5. The consumer thread does not give up the lock that the producer thread requires before waiting, so the producer thread also continues to wait. Note If I had access to the private lock used by BlockingQueue implementations, I would have used put() and take(), and also would have called Lock’s lock() and unlock() methods on that lock. The resulting application would then have been identical (from a lock perspective) to Listing 4-27’s PC application, which used synchronized twice for each of the producer and consumer threads. Run this application and you will discover that it generates the same output as Listing 4-27’s PC application. 418 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Atomic Variables The java.util.concurrent.atomic package provides Atomic-prefixed classes (e.g., AtomicLong) that support lock-free, thread-safe operations on single variables. Each class declares methods such as get() and set() to read and write this variable without the need for external synchronization. Listing 4-23 declared a small utility class named ID for returning unique long integer identifiers via ID’s getNextID() method. Because this method was not synchronized, multiple threads could obtain the same identifier. Listing 6-5 fixes this problem by including reserved word synchronized in the method header. Listing 6-5. Returning unique identifiers in a thread-safe manner via synchronized class ID { private static long nextID = 0; static synchronized long getNextID() { return nextID++; } } Although synchronized is appropriate for this class, excessive use of this reserved word in more complex classes can lead to deadlock, starvation, or other problems. Listing 6-6 shows you how to avoid these assaults on a concurrent application’s liveness (the ability to execute in a timely manner) by replacing synchronized with an atomic variable. Listing 6-6. Returning unique IDs in a thread-safe manner via AtomicLong import java.util.concurrent.atomic.AtomicLong; class ID { private static AtomicLong nextID = new AtomicLong(0); static long getNextID() { return nextID.getAndIncrement(); } } In Listing 6-6, I have converted nextID from a long to an AtomicLong instance, initializing this object to 0. I have also refactored the getNextID() method to call AtomicLong’s getAndIncrement() method, which increments the AtomicLong instance’s internal long integer variable by 1 and returns the previous value in one indivisible step. Additional Concurrency Utilities As well as supporting the Java 5-introduced concurrency utilities, Java 7 introduces a pair of concurrency utilities that improve performance, which is achieved in part by taking full advantage of multiple processors/cores. These utilities consist of the java.util.concurrent.ThreadLocalRandom class and the Fork/Join Framework. 419 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS ThreadLocalRandom The ThreadLocalRandom class describes a random number generator that is isolated to the current thread. In other words, it can be accessed from the current thread only. As with the global random number generator used by the java.lang.Math class (and which I discuss later in this chapter), a ThreadLocalRandom instance is initialized with an internally generated seed (starting value) that may not otherwise be modified. When applicable, use of ThreadLocalRandom rather than calls to Math.random() in concurrent programs will typically result in much less overhead and contention. To use this class, first invoke ThreadLocalRandom’s static ThreadLocalRandom current() method to return the current thread’s ThreadLocalRandom instance. Continue by invoking one of ThreadLocalRandom’s “next” methods, such as double nextDouble(double n), which returns a pseudorandom, uniformly distributed double value between 0 (inclusive) and the specified value n (exclusive). The argument passed to n is the upper bound on the random number to be returned and must be positive; otherwise, IllegalArgumentException is thrown. The following example provides a demonstration via another “next” method: int r = ThreadLocalRandom.current().nextInt(20, 40); This book was purchased by [email protected] This example invokes ThreadLocalRandom’s int nextInt(int least, int bound) method to return a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound (exclusive). In this example, least is 20, which is the smallest value that can be returned, and bound is 40, which is one integer greater than the highest value (39) that can be returned. Note ThreadLocalRandom leverages thread-local variables, which I discussed in Chapter 4’s coverage of Java’s Threading API. Fork/Join Framework There is always a need for code to execute faster. Historically, this need was addressed by increasing microprocessor speeds and/or by supporting multiple processors. However, somewhere around 2003, microprocessor speeds stopped increasing because of natural limits. To compensate, processor manufacturers started to add multiple processing cores to their processors, to increase speed through massive parallelism. Note Parallelism refers to running threads/tasks simultaneously through some combination of multiple processors and cores. In contrast, concurrency is a more generalized form of parallelism in which threads run simultaneously or appear to run simultaneously through task switching, also known as virtual parallelism. Some people further characterize concurrency as a property of a program or operating system and parallelism as the run-time behavior of executing multiple tasks simultaneously. 420 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Java supports concurrency via the Threading API and concurrency utilities such as thread pools. The problem with concurrency is that it doesn’t maximize the use of available processor/core resources. For example, suppose you have created a sorting algorithm that divides an array into two halves, assigns two threads to sort each half, and merges the results after both threads finish. Let’s assume that each thread runs on a different processor. Because different amounts of element reordering may occur in each half of the array, it’s possible that one thread will finish before the other thread and must wait before the merge can happen. In this case, a processor resource is wasted. This problem (and the related problems of the code being verbose and harder to read) can be solved by recursively breaking a task into subtasks and combining results. These subtasks run in parallel and complete approximately at the same time (if not at the same moment), where their results are merged and passed up the stack to the previous layer of subtasks. Hardly any processor time is wasted through waiting, and the recursive code is less verbose and (usually) easier to understand. Java provides the Fork/Join Framework to implement this scenario. Fork/Join consists of a special executor service and thread pool. The executor service makes a task available to the framework, and this task is broken down into smaller tasks that are forked (executed by different threads) from the pool. A task waits until joined (its subtasks finish). Fork/Join uses work stealing to minimize thread contention and overhead. Each worker thread from a pool of worker threads has its own double-ended work queue and pushes new tasks to this queue. It reads the task from the head of the queue. If the queue is empty, the worker thread tries to get a task from the tail of another queue. Stealing is infrequent because worker threads put tasks into their queues in a last-in, first-out (LIFO) order, and the size of work items gets smaller as a problem is divided into subproblems. You start by giving the tasks to a central worker and it keeps dividing them into smaller tasks. Eventually all the workers have something to do with minimal synchronization. Fork/Join largely consists of the java.util.concurrent package’s ForkJoinPool, ForkJoinTask, ForkJoinWorkerThread, RecursiveAction, and RecursiveTask classes: • ForkJoinPool is an ExecutorService implementation for running ForkJoinTasks. A ForkJoinPool instance provides the entry point for submissions from nonForkJoinTask clients, as well as providing management and monitoring operations. • ForkJoinTask is the abstract base class for tasks that run within a ForkJoinPool context. A ForkJoinTask instance is a thread-like entity that is much lighter weight than a normal thread. Huge numbers of tasks and subtasks may be hosted by a small number of actual threads in a ForkJoinPool, at the price of some usage limitations. • ForkJoinWorkerThread describes a thread managed by a ForkJoinPool instance, which executes ForkJoinTasks. • RecursiveAction describes a recursive resultless ForkJoinTask. • RecursiveTask describes a recursive result-bearing ForkJoinTask. The Java documentation provides examples of RecursiveAction-based tasks (such as sorting) and RecursiveTask-based tasks (such as computing Fibonacci numbers). You can also use RecursiveAction to accomplish matrix multiplication (see http://en.wikipedia.org/wiki/Matrix_multiplication). For example, suppose that you’ve created Listing 6-7’s Matrix class to represent a matrix consisting of a specific number of rows and columns. 421 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Listing 6-7. A class for representing a two-dimensional table class Matrix { private double[][] matrix; Matrix(int nrows, int ncols) { matrix = new double[nrows][ncols]; } int getCols() { return matrix[0].length; } int getRows() { return matrix.length; } double getValue(int row, int col) { return matrix[row][col]; } void setValue(int row, int col, double value) { matrix[row][col] = value; } } Listing 6-8 demonstrates the single-threaded approach to multiplying two Matrix instances: Listing 6-8. Multiplying two Matrix instances via the standard matrix-multiplication algorithm class MatMult { public static void main(String[] { Matrix a = new Matrix(1, 3); a.setValue(0, 0, 1); // | 1 2 a.setValue(0, 1, 2); a.setValue(0, 2, 3); dump(a); Matrix b = new Matrix(3, 2); b.setValue(0, 0, 4); // | 4 7 b.setValue(1, 0, 5); // | 5 8 b.setValue(2, 0, 6); // | 6 9 b.setValue(0, 1, 7); b.setValue(1, 1, 8); b.setValue(2, 1, 9); dump(b); dump(multiply(a, b)); } static void dump(Matrix m) 422 args) 3 | | | | CHAPTER 6 TOURING ADDITIONAL UTILITY APIS { for (int i = 0; i < m.getRows(); i++) { for (int j = 0; j < m.getCols(); j++) System.out.print(m.getValue(i, j)+" "); System.out.println(); } System.out.println(); } static Matrix multiply(Matrix a, Matrix b) { if (a.getCols() != b.getRows()) throw new IllegalArgumentException("rows/columns mismatch"); Matrix result = new Matrix(a.getRows(), b.getCols()); for (int i = 0; i < a.getRows(); i++) for (int j = 0; j < b.getCols(); j++) for (int k = 0; k < a.getCols(); k++) result.setValue(i, j, result.getValue(i, j)+a.getValue(i, k)* b.getValue(k, j)); return result; } } Listing 6-8’s MatMult class declares a multiply() method that demonstrates matrix multiplication. After verifying that the number of columns in the first Matrix (a) equals the number of rows in the second Matrix (b), which is essential to the algorithm, multiply() creates a Matrix named result and enters a sequence of nested loops to perform the multiplication. The essence of these loops is as follows: For each row in a, multiply each of that row’s column values by the corresponding column’s row values in b. Add together the results of the multiplications, and store the overall total in result at the location specified via the row index (i) in a and the column index (j) in b. When you run this application, it generates the following output, which indicates that a 1-row-by-3column matrix multiplied by a 3-row-by-2 column matrix results in a 1-row-by-2-column matrix: 1.0 2.0 3.0 4.0 7.0 5.0 8.0 6.0 9.0 32.0 50.0 3 Computer scientists classify this algorithm as O(n ), which is read “big-oh of n-cubed” or “approximately n-cubed.” This notation is an abstract way of classifying the algorithm’s performance (without being bogged down in specific details such as microprocessor speed). A O(n3) classification indicates very poor performance, and this performance worsens as the sizes of the matrixes being multiplied increase. The performance can be improved (on multiprocessor and/or multicore platforms) by assigning each row-by-column multiplication task to a separate thread-like entity. Listing 6-9 shows you how to accomplish this scenario in the context of the Fork/Join Framework. 423 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Listing 6-9. Multiplying two matrixes via the Fork/Join Framework import java.util.ArrayList; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; class MatMult extends RecursiveAction { private Matrix a, b, c; private int row; MatMult(Matrix a, Matrix b, Matrix c) { this(a, b, c, -1); } MatMult(Matrix a, Matrix b, Matrix c, int row) { if (a.getCols() != b.getRows()) throw new IllegalArgumentException("rows/columns mismatch"); this.a = a; this.b = b; this.c = c; this.row = row; } @Override public void compute() { if (row == -1) { List<MatMult> tasks = new ArrayList<>(); for (int row = 0; row < a.getRows(); row++) tasks.add(new MatMult(a, b, c, row)); invokeAll(tasks); } else multiplyRowByColumn(a, b, c, row); } static void multiplyRowByColumn(Matrix a, Matrix b, Matrix c, int row) { for (int j = 0; j < b.getCols(); j++) for (int k = 0; k < a.getCols(); k++) c.setValue(row, j, c.getValue(row, j)+a.getValue(row, k)* b.getValue(k, j)); } static void dump(Matrix m) { for (int i = 0; i < m.getRows(); i++) { for (int j = 0; j < m.getCols(); j++) System.out.print(m.getValue(i, j)+" "); 424 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS System.out.println(); } System.out.println(); } public static void main(String[] args) { Matrix a = new Matrix(2, 3); a.setValue(0, 0, 1); // | 1 2 3 | a.setValue(0, 1, 2); // | 4 5 6 | a.setValue(0, 2, 3); a.setValue(1, 0, 4); a.setValue(1, 1, 5); a.setValue(1, 2, 6); dump(a); Matrix b = new Matrix(3, 2); b.setValue(0, 0, 7); // | 7 1 | b.setValue(1, 0, 8); // | 8 2 | b.setValue(2, 0, 9); // | 9 3 | b.setValue(0, 1, 1); b.setValue(1, 1, 2); b.setValue(2, 1, 3); dump(b); Matrix c = new Matrix(2, 2); ForkJoinPool pool = new ForkJoinPool(); pool.invoke(new MatMult(a, b, c)); dump(c); } } Listing 6-9 presents a MatMult class that extends RecursiveAction. To accomplish meaningful work, RecursiveAction’s void compute() method is overridden. Note Although compute() is normally used to subdivide a task into subtasks recursively, I’ve chosen to handle the multiplication task somewhat differently (for brevity and simplicity). After creating Matrixes a and b, Listing 6-9’s main() method creates Matrix c and instantiates ForkJoinPool. It then instantiates MatMult, passing these three Matrix instances as arguments to the MatMult(Matrix a, Matrix b, Matrix c) constructor, and calls ForkJoinPool’s T invoke(ForkJoinTask<T> task) method to start running this initial task. This method does not return until the initial task and all of its subtasks complete. The MatMult(Matrix a, Matrix b, Matrix c) constructor invokes the MatMult(Matrix a, Matrix b, Matrix c, int row) constructor, specifying -1 as row’s value. This value is used by compute(), which is invoked as a result of the aforementioned invoke() method call, to distinguish between the initial task and subtasks. When compute() is initially called (row equals -1), it creates a List of MatMult tasks and passes this List to RecursiveAction’s Collection<T> invokeAll(Collection<T> tasks) method (inherited from ForkJoinTask). This method forks all the List collection’s tasks, which will start to execute. It then waits 425 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS until the invokeAll() method returns (which also joins to all these tasks), which happens when the boolean isDone() method (also inherited from ForkJoinTask) returns true for each task. Notice the tasks.add(new MatMult(a, b, c, row)); method call. This call assigns a specific row value to a MatMult instance. When invokeAll() is called, each task’s compute() method is called and detects a different value (other than -1) assigned to row. It then executes multiplyRowByColumn(a, b, c, row); for its specific row. When you run this application (java MatMult), it generates the following output: 1.0 2.0 3.0 4.0 5.0 6.0 7.0 1.0 8.0 2.0 9.0 3.0 50.0 14.0 122.0 32.0 Objects Java 7’s new java.util.Objects class consists of class methods for operating on objects. These utilities include null-safe or null-tolerant methods for comparing two objects, computing the hash code of an object, requiring that a reference not be null, and returning a string for an object. Table 6-3 describes Objects’ class methods. Table 6-3. Objects Methods 426 Method Description <T> int compare(T a, T b, Comparator<? super T> c) Return 0 when the first two arguments are identical (including the case where both arguments are the null reference), and the result of invoking c.compare(a, b) otherwise. An instance of the NullPointerException class may or may not be thrown depending on the java.util.Comparator argument’s ordering policy for null references (if there is such a policy). (I discussed Comparator in Chapter 5.) boolean deepEquals(Object a, Object b) Return true when the passed arguments are deeply equal (discussed later). Otherwise, this method returns false. Two null references are considered to be deeply equal. If both arguments are arrays, the algorithm followed by Arrays.deepEquals() is used to determine equality. Otherwise, equality is determined by calling the first argument’s equals() method. (I introduced java.util.Arrays in Chapter 5.) boolean equals(Object a, Object b) Return true when the passed arguments are equal to each other (including the scenario where both arguments are null). Otherwise, this method returns false (including CHAPTER 6 TOURING ADDITIONAL UTILITY APIS scenarios where only one argument is null). If neither argument is null, equality is determined by calling the first argument’s equals() method. int hash(Object... values) Generate a hash code for a sequence of object arguments. The hash code is generated as if all arguments were put into an array and that array was hashed by calling Arrays.hashCode(Object[]).When a single object is passed to values, hash()’s returned value does not equal the hash code of that object. To obtain a single object’s hash code, call hashCode(Object). int hashCode(Object o) Return the hash code of a nonnull argument and 0 for the null argument. <T> T requireNonNull(T obj) Test the passed object reference for nullness. It either returns the nonnull reference stored in obj or throws NullPointerException when obj contains the null reference. <T> T requireNonNull(T obj, String message) Test the passed object reference for nullness. It either returns the nonnull reference stored in obj or throws NullPointerException when obj contains the null reference. The thrown NullPointerException instance contains the message provided by message. String toString(Object o) Return the result of calling toString() for a nonnull argument and "null" for a null argument. String toString(Object o, String nullDefault) Return the result of calling toString() on the first argument (passed to o) when that argument is not null; otherwise, this method returns the second argument (passed to nullDefault). Objects implements the null-tolerant compare() method to first compare its arguments for object identity by using == before calling the provided Comparator. The equals() and deepEquals() methods define equivalence relations over object references. Unlike Object.equals(Object o), Objects.equals(Object a, Object b) handles null values, returning true when both arguments are null, or when the first argument is nonnull and a.equals(b) returns true. The deepEquals() method is used in the context of arrays (including nested arrays) to determine if two arrays are deeply equal (they are both null or they contain the same number of elements and all corresponding pairs of elements in the two arrays are deeply equal). This method’s two (possibly null) arguments, denoted by e1 and e2 below, are deeply equal when any of the following conditions hold: • e1 and e2 are arrays of object reference types, and Arrays.deepEquals(e1, e2) would return true • e1 and e2 are arrays of the same primitive type, and the appropriate overloading of Arrays.equals(e1, e2) would return true. 427 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS • e1 == e2 • e1.equals(e2) would return true. Equality implies deep equality, but the converse isn’t necessarily true. In the following example, x and y are deeply equal but are not equal: Object common = "string"; Object[] x = {"string"}; Object[] y = {"string"}; System.out.println("x == y: "+(x == y)); // false (two different references) System.out.println("Objects.equals(x, y): "+Objects.equals(x, y)); // false System.out.println("Objects.deepEquals(x, y): "+Objects.deepEquals(x, y)); // true Arrays x and y are not equal because they contain two different references and Objects.equals() is using reference equality (comparing their references) in this context. (Object equality, or comparing object contents, occurs when a class overrides Object’s equals() method.) However, these arrays are deeply equal because x and y are both arrays of object reference types and Arrays.deepEquals(x, y) would return true. Note Unlike the java.lang.Object class, which is automatically imported because of its java.lang prefix, you must explicitly import Objects into your source code (import java.util.Objects;) when you want to avoid having to specify the java.util prefix. The Java documentation for the requireNonNull() methods states that they are designed primarily for doing parameter validation in methods and constructors. The idea is to check a method’s or a constructor’s parameter values for null references before attempting to use these references later in the method or constructor, and avoid potential NullPointerExceptions. Listing 6-10 provides a demonstration. Listing 6-10. Testing constructor parameters for null reference arguments import java.util.Objects; class Employee { private String firstName, lastName; Employee(String firstName, String lastName) { try { firstName = Objects.requireNonNull(firstName); lastName = Objects.requireNonNull(lastName, "lastName shouldn't be null"); lastName = Character.toUpperCase(lastName.charAt(0))+ lastName.substring(1); this.firstName = firstName; this.lastName = lastName; 428 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS } catch (NullPointerException npe) { // In lieu of a more sophisticated logging mechanism, and also for // brevity, I output the exception's message to standard output. System.out.println(npe.getMessage()); } } String getName() { return firstName+" "+lastName; } public static void main(String[] args) { Employee e1 = new Employee(null, "Doe"); Employee e2 = new Employee("John", null); Employee e3 = new Employee("John", "doe"); System.out.println(e3.getName()); } } Listing 6-10’s Employee constructor first invokes Objects.requireNonNull() on each argument value passed to its firstName and lastName parameters. If either argument value is the null reference, NullPointerException is instantiated and thrown; otherwise, the requireNonNull() method returns the argument value, which is guaranteed to be nonnull. It is now safe to invoke lastName.charAt(), which returns the first character from the string on which this method is called. This character is passed to Character’s toUpperCase() utility method, which returns the character when it does not represent a lowercase letter, or the uppercase equivalent of the lowercase letter. After toUpperCase() returns, the (potentially uppercased) letter is prepended to the rest of the string, resulting in a last name starting with an uppercase letter. (Assume that the name consists of letters only.) Listing 6-10’s Objects.requireNonNull() method calls offer a more compact alternative to the following example, which demonstrates how requireNonNull(T obj, String message)’s message parameter is used: if (firstName == null) throw new NullPointerException(); if (lastName == null) throw new NullPointerException("lastName shouldn't be null"); Compile Listing 6-10 (javac Employee.java) and run the resulting application (java Employee). You should observe the following output: null lastName shouldn't be null John Doe As Listing 6-10 reveals, the Objects class’s methods were introduced to promote null safety by reducing the likelihood of a NullPointerException being thrown unintentionally. As another example, Employee e = null; String s = e.toString(); results in a thrown NullPointerException instance because you cannot invoke toString() on the null reference stored in e. In contrast, Employee e = null; String s = Objects.toString(e); doesn’t result in a thrown NullPointerException instance because Objects.toString() returns "null" when it detects that e contains the null reference. Rather than having 429 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS to explicitly test a reference for null, as in if (e != null) { String s = e.toString(); /* other code here */ }, you can offload the null-checking to the Objects class’s various methods. These methods were also introduced to avoid the “reinventing the wheel” syndrome. Many developers have repeatedly written methods that perform similar operations, but do so in a null-safe manner. The inclusion of Objects in Java’s standard class library standardizes this common functionality. Random Chapter 4 introduced you to the Math class’s random() method. If you were to investigate this method’s source code, you would discover the following implementation: This book was purchased by [email protected] private static Random randomNumberGenerator; private static synchronized Random initRNG() { Random rnd = randomNumberGenerator; return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd; } public static double random() { Random rnd = randomNumberGenerator; if (rnd == null) rnd = intRNG(); return rnd.nextDouble(); } This implementation, which demonstrates lazy initialization (not initializing something until it is first needed, in order to improve performance), shows you that Math’s random() method is implemented in terms of a class named Random, which is located in the java.util package. Random instances generate sequences of random numbers and are known as random number generators. Note These numbers are not truly random because they are generated from a mathematical algorithm. As a result, they are often referred to as pseudorandom numbers. However, it is often convenient to drop the “pseudo” prefix and refer to them as random numbers. Random generates its sequence of random numbers by starting with a special 48-bit value that is known as a seed. This value is subsequently modified by a mathematical algorithm, which is known as a linear congruential generator. Note Check out Wikipedia’s “Linear congruential generator” entry (http://en.wikipedia.org/wiki/Linear_congruential_generator) to learn about this algorithm for generating random numbers. 430 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Random declares a pair of constructors: • Random() creates a new random number generator. This constructor sets the seed of the random number generator to a value that is very likely to be distinct from any other call to this constructor. • Random(long seed) creates a new random number generator using its seed argument. This argument is the initial value of the random number generator’s internal state, which the protected int next(int bits) method maintains. Note The next() method, which is used by the other methods, is protected so that subclasses can change the generator implementation from that shown below protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed*multiplier+addend)&mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int) (nextseed >>> (48-bits)); } to something different. For a subclassing example, check out “Subclassing java.util.Random” (http://www.javamex.com/tutorials/random_numbers/java_util_random_subclassing.shtml). Because Random() does not take a seed argument, the resulting random number generator always generates a different sequence of random numbers. This explains why Math.random() generates a different sequence each time an application starts running. Tip Random(long seed) gives you the opportunity to reuse the same seed value, allowing the same sequence of random numbers to be generated. You will find this capability useful when debugging a faulty application that involves random numbers. Random(long seed) calls the void setSeed(long seed) method to set the seed to the specified value. If you call setSeed() after instantiating Random, the random number generator is reset to the state that it was in immediately after calling Random(long seed). The previous code fragment demonstrates Random’s double nextDouble() method, which returns the next pseudorandom, uniformly distributed double precision floating-point value between 0.0 and 1.0 in this random number generator’s sequence. 431 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS Random also declares the following methods for returning other kinds of values: • boolean nextBoolean() returns the next pseudorandom, uniformly distributed Boolean value in this random number generator’s sequence. Values true and false are generated with (approximately) equal probability. • void nextBytes(byte[] bytes) generates pseudorandom byte integer values and stores them in the bytes array. The number of generated bytes is equal to the length of the bytes array. • float nextFloat() returns the next pseudorandom, uniformly distributed floating-point value between 0.0 and 1.0 in this random number generator’s sequence. • double nextGaussian() returns the next pseudorandom, Gaussian (“normally”) distributed double precision floating-point value with mean 0.0 and standard deviation 1.0 in this random number generator’s sequence. • int nextInt() returns the next pseudorandom, uniformly distributed integer 32 value in this random number generator’s sequence. All 2 possible integer values are generated with (approximately) equal probability. • int nextInt(int n) returns a pseudorandom, uniformly distributed integer value between 0 (inclusive) and the specified value (exclusive), drawn from this random number generator’s sequence. All n possible integer values are generated with (approximately) equal probability. • long nextLong() returns the next pseudorandom, uniformly distributed long integer value in this random number generator’s sequence. Because Random uses a seed with only 48 bits, this method will not return all possible 64-bit long integer values. The java.util.Collections class declares a pair of shuffle() methods for shuffling the contents of a list. In contrast, the Arrays class does not declare a shuffle() method for shuffling the contents of an array. Listing 6-11 addresses this omission. Listing 6-11. Shuffling an array of integers import java.util.Random; class Shuffler { public static void main(String[] args) { Random r = new Random(); int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; for (int i = 0; i < array.length; i++) { int n = r.nextInt(array.length); // swap array[i] with array[n] int temp = array[i]; array[i] = array[n]; array[n] = temp; 432 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS } for (int i = 0; i < array.length; i++) System.out.print(array[i]+" "); System.out.println(); } } Listing 6-11 presents a simple recipe for shuffling an array of integers—this recipe could be generalized. For each array entry from the start of the array to the end of the array, this entry is swapped with another entry whose index is chosen by int nextInt(int n). When you run this application, you will observe a shuffled sequence of integers that is similar to the following sequence that I observed: 7 1 5 2 9 8 6 4 3 0 EXERCISES The following exercises are designed to test your understanding of the concurrency utilities, Objects, and Random: 1. The Java documentation for the Semaphore class presents a Pool class that demonstrates how a semaphore can control access to a pool of items. Because Pool is incomplete, introduce a single resource (replace protected Object[] items = ... with an array containing this resource in its single entry) and then demonstrate Pool’s getItem() and putItem() methods in the context of a pair of threads launched from the main() method of a SemaphoreDemo class. 2. Create an EqualsDemo application to play with Objects’ deepEquals() method. As well as an EqualsDemo class, this application declares Car and Wheel classes. A Car instance contains (typically four) Wheel instances, and a Wheel instance contains a brand name. Each of Car and Wheel must override Object’s equals() method but does not have to override hashCode() in this example. Your main() method should contain the following code and generate the output shown in the comments: Car[] Car[] Car[] Car[] Car[] cars1 cars2 cars3 cars4 cars5 = = = = = { { { { { new Car(4, "Goodyear"), new Car(4, "Goodyear") }; new Car(4, "Goodyear"), new Car(4, "Goodyear") }; new Car(4, "Michelin"), new Car(4, "Goodyear") }; new Car(3, "Goodyear"), new Car(4, "Goodyear") }; new Car(4, "Goodyear"), new Car(4, "Goodyear"), new Car(3, "Michelin") }; System.out.println(Objects.deepEquals(cars1, cars2)); // Output: true System.out.println(Objects.deepEquals(cars1, cars3)); // Output: false System.out.println(Objects.deepEquals(cars1, cars4)); // Output: false System.out.println(Objects.deepEquals(cars1, cars5)); // Output: false The comments reveal that two arrays are deeply equal when they contain the same number of equal elements. 433 CHAPTER 6 TOURING ADDITIONAL UTILITY APIS 3. Create a Die application that uses Random to simulate the role of a die (a single dice). Output the value. Summary Java 5 introduced the concurrency utilities to simplify the development of concurrent applications. The concurrency utilities are organized into executor, synchronizer, concurrent collection, lock, atomic variable, and additional utilities categories, and leverage the low-level Threading API in their implementations. An executor decouples task submission from task-execution mechanics and is described by the Executor, ExecutorService, and ScheduledExecutorService interfaces. A synchronizer facilitates common forms of synchronization: countdown latches, cyclic barriers, exchangers, phasers, and semaphores are commonly used synchronizers. A concurrent collection is an extension to the Collections Framework. A lock supports high-level locking and can associate with conditions in a manner that is distinct from built-in synchronization and monitors. An atomic variable encapsulates a single variable, and supports lock-free, thread-safe operations on that variable. Java 7’s new ThreadLocalRandom class describes a random number generator that is isolated to the current thread, and its new Fork/Join Framework lets you recursively break a task into subtasks and combine results to make maximum use out of multiple processors and/or processor cores. The new Objects class consists of class methods for operating on objects. These utilities include null-safe or null-tolerant methods for comparing two objects, computing the hash code of an object, requiring that a reference not be null, and returning a string for an object. The Math class’s random() method is implemented in terms of the Random class, whose instances are known as random number generators. Random generates a sequence of random numbers by starting with a special 48-bit seed. This value is subsequently modified via a mathematical algorithm that is known as a linear congruential generator. The examples in this chapter and its predecessors have leveraged the underlying platform’s Standard I/O facility to create character-based user interfaces. However, Java also lets you create GUIs to achieve more compelling user interfaces. Chapter 7 introduces you to Java’s APIs for creating and enriching GUIs. 434 CHAPTER 7 Creating and Enriching Graphical User Interfaces The applications presented in previous chapters featured Standard I/O-based user interfaces. Although these simple character-oriented user interfaces are convenient for demonstrating Java features or for interacting with small utility applications (e.g., Chapter 3’s StubFinder application), they are inadequate for more sophisticated needs, such as filling out forms or viewing HTML pages. However, Java also provides APIs that let you create and enrich more sophisticated graphical user interfaces (GUIs). Abstract Window Toolkit (AWT) is Java’s original GUI-oriented API. After introducing AWT to Java, Sun Microsystems introduced Java Foundation Classes (JFC) as an AWT superset with many new capabilities. JFC’s main APIs are Swing (for creating more sophisticated GUIs), Accessibility (for supporting assistive technologies), Java 2D (for creating high-quality graphics), and Drag and Drop (for dragging and dropping AWT/Swing GUI components, such as buttons or textfields). Chapter 7 continues to explore the standard class library by introducing you to AWT, Swing, and Java 2D. Appendix C introduces you to Accessibility and Drag and Drop. Abstract Window Toolkit Abstract Window Toolkit (AWT) is Java’s original windowing system-independent API for creating GUIs that are based on components, containers, layout managers, and events. AWT also supports graphics, colors, fonts, images, data transfer, and more. The standard class library organizes AWT’s many types into the java.awt package and subpackages. However, not all java.awt types and subpackages belong to AWT. For example, java.awt.Graphics2D and java.awt.geom belong to Java 2D. This arrangement exists because the java.awt-based package structure provides a natural fit for various non-AWT types. (AWT is often viewed as part of JFC nowadays.) This section introduces you to AWT by first presenting toolkits. It then explores components, containers, layout managers, and events. After exploring graphics, colors, and fonts, the section focuses on images. It closes by discussing AWT’s support for data transfer. AWT HISTORY Before JDK 1.0’s release (on January 23, 1996), developers at Sun Microsystems were tasked with abstracting the various windowing systems of the day and their attendant widgets (GUI controls, such as buttons—Java refers to GUI controls as components) into a portable windowing system that Java applications could target. AWT was born and was included in JDK 1.0. (Legend has it [see 435 CHAPTER 7 CREATING AND ENRICHING GRAPHICAL USER INTERFACES http://www.cs.jhu.edu/~scott/oos/java/doc/TIJ3/html/TIJ316.htm, for example] that the first AWT version had to be designed and implemented in one month.) The JDK 1.0.1 and 1.0.2 releases corrected various AWT bugs, and JDK 1.1 offered an improved eventhandling model that greatly simplified how applications respond to GUI events (such as button clicks and key presses). Subsequent JDK releases brought about additional improvements. For example, JDK 1.2 introduced JFC, JDK 6 introduced the Desktop, Splash Screen, and System Tray APIs, and JDK 7 standardized the support for translucent and shaped windows first introduced in JDK 6 update 10 (build 12). Appendix C covers Desktop, Splash Screen, System Tray, and translucent/shaped windows. Toolkits AWT uses toolkits to abstract over windowing systems. A toolkit is a concrete implementation of AWT’s abstract java.awt.Toolkit class. AWT provides a separate toolkit for each windowing system used by the Windows, Solaris, Linux, and Mac OS platforms. Toolkit declares various methods that AWT calls to obtain information about the platform’s windowing system, and to perform various windowing system-specific tasks. For example, void beep() emits an audio beep. Most applications should not call any of Toolkit’s methods directly; they are intended for use by AWT. However, you might occasionally find it helpful to call some of these methods. For example, you might want your application to sound one or more beeps when a long-running task finishes, to alert the user who might not be looking at the screen. You can accomplish this task by specifying code that’s similar to the following: Toolkit toolkit = Toolkit.getDefaultToolkit(); for (int i = 0 ; i < 5; i++) { toolkit.beep(); try { Thread.sleep(200); } catch (InterruptedException ie) {} } This example reveals that you must obtain a Toolkit instance before you can call a Toolkit method, and that you do so by calling Toolkit’s Toolkit getDefaultToolkit() class method. It also reveals that you might want to place a small delay between successive beeps to ensure that each beep is distinct. Components, Containers, Layout Managers, and Events AWT lets you create GUIs that are based on components, containers, layout managers, and events. A component is a graphical widget that appears in a window on the screen; a label, a button, or a textfield is an example. A window is represented by a special component known as a container. A layout manager is an object that organizes components and containers within a container. It is used to create useful GUIs (e.g., a form consisting of labels, textfields, and buttons). An event is an object describing a button click or other GUI interaction. Applications register event listener objects with components to listen for specific events so that application code can respond to them. 436 CHAPTER 7 CREATING AND ENRICHING GRAPHICAL USER INTERFACES Components Overview AWT provides a wide variety of component classes in the java.awt package. Figure 7-1 presents the class hierarchy for AWT’s nonmenu component classes. Figure 7-1. AWT’s nonmenu component class hierarchy is rooted in java.awt.Component. AWT’s abstract Component class is the root class for all AWT nonmenu components (and Swing components). Directly beneath Component are Button, Canvas, Checkbox, Choice, Container, Label, List, Scrollbar, and TextComponent: • Button describes a clickable label. • Canvas describes a blank rectangular area. You would subclass Canvas to introduce your own AWT components. • Checkbox describes a true/false choice. You can use Checkbox with java.awt.CheckboxGroup to create a set of mutually exclusive radio buttons. • Choice describes a drop-down list (also known as a pop-up menu) of strings. • Container describes a component that stores other components. This nesting capability lets you create GUIs of arbitrary complexity and is very powerful. (Being able to represent containers as components is an example of the Composite design pattern, which is presented on page 163 of Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides [Addison-Wesley, 1995; ISBN: 0201633612].) • Label describes a single line of static text as a visual aid to the user. • List describes a non-drop-down list of strings. 437 CHAPTER 7 CREATING AND ENRICHING GRAPHICAL USER INTERFACES • Scrollbar describes a range of values. • TextComponent describes any component that inputs text. Its TextArea subclass describes a text component for inputting multiple lines of text, whereas its TextField subclass describes a text component for inputting a single line of text. Figure 7-2 presents the class hierarchy for menu component classes. Figure 7-2. AWT’s menu component class hierarchy is rooted in java.awt.MenuComponent. AWT’s abstract MenuComponent class (which doesn’t extend Component) is the root class for all AWT menu components. Directly beneath MenuComponent are MenuBar and MenuItem: • MenuBar encapsulates the windowing system concept of a menubar bound to a frame window. It contains a sequence of Menu components, where each Menu component contains a sequence of MenuItem components. • MenuItem describes a single menuitem. Its CheckboxMenuItem subclass describes a menuitem that’s implemented via a checkbox. Its Menu subclass describes a pulldown menu component that’s deployed from a menu bar. (Menu extends MenuItem to create arbitrarily complex menus.) Menu is subclassed by PopupMenu to describe a menu that can be dynamically popped up at a specified position within a component. Component declares many nonmenu component-oriented methods. For example, Component declares the following methods to inform the caller about the component’s displayable, visible, and showing status: 438 • boolean isDisplayable() returns true when a component is in the displayable state (the component is connected to a native screen resource [defined shortly], typically by being added to a container). • boolean isVisible() returns true when a component is in the visible state (the component appears on the screen). The companion void setVisible(boolean b) method lets you show (b is true) or hide (b is false) a component. • boolean isShowing() returns true when a component is in the showing state (the component is visible and is contained in a container that is also visible and showing). This method is useful for determining whether or not a component has been obscured by another component. It returns false when obscured, whereas isVisible() would continue to return true. CHAPTER 7 CREATING AND ENRICHING GRAPHICAL USER INTERFACES MenuComponent’s repertoire of methods is much shorter. However, it shares some commonality with Component. For example, both classes declare a method for specifying the component’s font. Some of Component’s and MenuComponent’s methods have been deprecated and should not be used. For example, Component declares java.awt.peer.ComponentPeer getPeer() and MenuComponent declares java.awt.peer.MenuComponentPeer getPeer(). Both deprecated methods hint at how AWT implements its predefined components. AWT leverages the platform’s windowing system to create various components. When you add a component to a container, AWT creates a peer object whose class implements a ComponentPeer or MenuComponentPeer subinterface. For example, AWT creates a java.awt.peer.ButtonPeer instance when you add a Button component class instance to a container. Note Each AWT toolkit implementation includes its own set of peer interface implementations. Behind the scenes, the component object communicates with the peer object, which communicates with native code in a JDK library. This code communicates with the platform’s windowing system, which manages the native screen resource (a native window) that appears on the screen. For example, when you add a Button instance to a container, AWT calls Component’s void addNotify() method, which obtains the current toolkit and calls its ButtonPeer createButton(Button target) method to create this toolkit’s Button peer. Ultimately, the windowing system is asked to create a button native screen resource. For example, on a 32-bit Windows operating system, the native screen resource could be obtained via a call to the CreateWindow() or CreateWindowEx() Win32 API function. AWT components except for those created from nonpredefined classes that directly extend Component or Container are known as heavyweight components because of their corresponding peer interfaces and native screen resources. Components created from custom Component and Container subclasses are known as lightweight components because they do not have peer interfaces and native screen resources (they reuse their closest ancestor’s peer, which is how Swing works). You can call Component’s boolean isLightweight() method to determine if a component is lightweight. Note Heavyweight and lightweight components can be mixed in a single component hierarchy provided that the entire hierarchy is valid (noncontainer components are correctly sized; container components have their contained components laid out). When the hierarchy is invalidated (e.g., after changing component bounds [width, height, and location relative to the component’s parent container], such as when changing a button’s text, or after adding/removing components to/from containers), AWT validates it by invoking Container’s void validate() method on the top-most invalid container of the hierarchy. As you explore the JDK documentation for the various component classes, you’ll discover many useful constructors and methods. For example, Button declares a Button(String label) constructor for initializing a button to the specified label text. Alternatively, you could call the Button() constructor to create a Button with no label. Regardless of which constructor you use, you can always call Button’s void setLabel(String label) and String getLabel() methods to specify and retrieve the label text that is 439 CHAPTER 7 CREATING AND ENRICHING GRAPHICAL USER INTERFACES displayed on the button. (Changing a button’s displayed text invalidates the button; AWT then performs validation, which causes the component hierarchy to be re-laid out.) Components are easy to create, as demonstrated by the following example, which creates a Yes button: Button btnYes = new Button("Yes"); Note I like to prefix a component variable to indicate its kind. For example, I prefix buttons with btn. Containers Overview This book was purchased by [email protected] Buttons, labels, textfields, and other components cannot be placed directly on the screen; they need to be placed in a container window that is placed directly on the screen. AWT provides several container classes in the java.awt package. Figure 7-3 presents their hierarchy. Figure 7-3. AWT’s container class hierarchy is rooted in Container. AWT’s Container class is the root class for all AWT containers. Directly beneath Container are Panel, ScrollPane, and Window: • Panel is the simplest container. It provides space in which an application can attach any other component, including other panels. • ScrollPane implements automatic horizontal and/or vertical scrolling for a single child (contained) component. A container that contains a component is referred to as that component’s parent. • Window is a top-level window with no borders. Its Dialog subclass describes a dialog box (a window for soliciting input from the user) and its Frame subclass describes a frame window (a top-level window with borders, including a titlebar). Dialog’s FileDialog subclass describes a dialog box for selecting a file. Container declares many container-oriented methods. For example, Component add(Component comp) appends component comp to the container, Component[] getComponents() returns an array of the container’s components, and int getComponentCount() returns the number of components in the container. Window declares a void pack() method for making a top-level window just large enough to display all its components at their preferred (natural) sizes. Also, pack() makes the window (and any owner of the window—dialog boxes are typically owned by other windows) displayable when not already displayable. 440 CHAPTER 7 CREATING AND ENRICHING GRAPHICAL USER INTERFACES Window also declares a void setSize(int width, int height) method that lets you size a window to a specific size (in pixels). Continuing from the previous example, suppose you want to add the Yes button to a panel (which might also contain a No button). The following example shows you how to accomplish this task: Panel pnl = new Panel(); pnl.add(btnYes); Layout Managers Overview Containers can contain components but cannot lay them out on the screen (e.g., in rows, in a grid, or in some other arrangement). Layout managers handle this task. A layout manager is typically associated with a container to lay out the container’s components. Note Layout managers provide a screen size-independent way to display a GUI. Without them, an application would have to obtain the current screen size and adapt container/component sizes to account for the screen size. Doing so could involve writing hundreds of lines of code, a tedious proposition at best. AWT provides several layout managers in the java.awt package: BorderLayout (lay out no more than five components in a container’s north, south, east, west, and center areas), CardLayout (treat each contained component as a card; only one card is visible at a time, and the container acts as a stack of cards), FlowLayout (arrange components in a horizontal row), GridBagLayout (l