1 Declarations and Access Control CERTIFICATION OBJECTIVES l Declare Classes & Interfaces l Develop Interfaces & Abstract Classes l Use Primitives, Arrays, Enums, & Legal Identifiers l Use Static Methods, JavaBeans Naming, & Var-Args 3 Two-Minute Drill Q&A Self Test Chapter 1: Declarations and Access Control W e assume that because you're planning on becoming certified, you already know the basics of Java. If you're completely new to the language, this chapter—and the rest of the book—will be confusing; so be sure you know at least the basics of the language before diving into this book. That said, we're starting with a brief, high-level refresher to put you back in the Java mood, in case you've been away for awhile. Java Refresher A Java program is mostly a collection of objects talking to other objects by invoking each other's methods. Every object is of a certain type, and that type is defined by a class or an interface. Most Java programs use a collection of objects of many different types. n Class A template that describes the kinds of state and behavior that objects of its type support. n Object At runtime, when the Java Virtual Machine (JVM) encounters the new keyword, it will use the appropriate class to make an object which is an instance of that class. That object will have its own state, and access to all of the behaviors defined by its class. n State (instance variables) Each object (instance of a class) will have its own unique set of instance variables as defined in the class. Collectively, the values assigned to an object's instance variables make up the object's state. n Behavior (methods) When a programmer creates a class, she creates methods for that class. Methods are where the class' logic is stored. Methods are where the real work gets done. They are where algorithms get executed, and data gets manipulated. Identifiers and Keywords All the Java components we just talked about—classes, variables, and methods— need names. In Java these names are called identifiers, and, as you might expect, there are rules for what constitutes a legal Java identifier. Beyond what's legal, Java Refresher though, Java programmers (and Sun) have created conventions for naming methods, variables, and classes. Like all programming languages, Java has a set of built-in keywords. These keywords must not be used as identifiers. Later in this chapter we'll review the details of these naming rules, conventions, and the Java keywords. Inheritance Central to Java and other object-oriented languages is the concept of inheritance, which allows code defined in one class to be reused in other classes. In Java, you can define a general (more abstract) superclass, and then extend it with more specific subclasses. The superclass knows nothing of the classes that inherit from it, but all of the subclasses that inherit from the superclass must explicitly declare the inheritance relationship. A subclass that inherits from a superclass is automatically given accessible instance variables and methods defined by the superclass, but is also free to override superclass methods to define more specific behavior. For example, a Car superclass class could define general methods common to all automobiles, but a Ferrari subclass could override the accelerate() method. Interfaces A powerful companion to inheritance is the use of interfaces. Interfaces are like a 100-percent abstract superclass that defines the methods a subclass must support, but not how they must be supported. In other words, an Animal interface might declare that all Animal implementation classes have an eat() method, but the Animal interface doesn't supply any logic for the eat() method. That means it's up to the classes that implement the Animal interface to define the actual code for how that particular Animal type behaves when its eat() method is invoked. Finding Other Classes As we'll see later in the book, it's a good idea to make your classes cohesive. That means that every class should have a focused set of responsibilities. For instance, if you were creating a zoo simulation program, you'd want to represent aardvarks with one class, and zoo visitors with a different class. In addition, you might have a Zookeeper class, and a Popcorn vendor class. The point is that you don't want a class that has both Aardvark and Popcorn behaviors (more on that in Chapter 2). Even a simple Java program uses objects from many different classes: some that you created, and some built by others (such as Sun's Java API classes). Java organizes classes into packages, and uses import statements to give programmers a consistent way to manage naming of, and access to, classes they need. The exam covers a lot of concepts related to packages and class access; we'll explore the details in this—and later—chapters. Certification Objective Identifiers & JavaBeans (Objectives 1.3 and 1.4) 1.3 Develop code that declares, initializes, and uses primitives, arrays, enums, and objects as static, instance, and local variables. Also, use legal identifiers for variable names. 1.4 Develop code that declares both static and non-static methods, and—if appropriate— use method names that adhere to the JavaBeans naming standards. Also develop code that declares and uses a variable-length argument list. Remember that when we list one or more Certification Objectives in the book, as we just did, it means that the following section covers at least some part of that objective. Some objectives will be covered in several different chapters, so you'll see the same objective in more than one place in the book. For example, this section covers declarations, identifiers, and JavaBeans naming, but using the things you declare is covered primarily in later chapters. So, we'll start with Java identifiers. The three aspects of Java identifiers that we cover here are n Legal Identifiers The rules the compiler uses to determine whether a name is legal. n Sun's Java Code Conventions Sun's recommendations for naming classes, variables, and methods. We typically adhere to these standards throughout the book, except when we're trying to show you how a tricky exam question might be coded. You won't be asked questions about the Java Code Conventions, but we strongly recommend that programmers use them. n JavaBeans Naming Standards The naming requirements of the JavaBeans specification. You don't need to study the JavaBeans spec for the exam, but you do need to know a few basic JavaBeans naming rules we cover in this chapter. Chapter 1: Declarations and Access Control Legal Identifiers Technically, legal identifiers must be composed of only Unicode characters, numbers, currency symbols, and connecting characters (like underscores). The exam doesn't dive into the details of which ranges of the Unicode character set are considered to qualify as letters and digits. So, for example, you won't need to know that Tibetan digits range from \u0420 to \u0f29. Here are the rules you do need to know: n Identifiers must start with a letter, a currency character ($), or a connecting character such as the underscore ( _ ). Identifiers cannot start with a number! n After the first character, identifiers can contain any combination of letters, currency characters, connecting characters, or numbers. n In practice, there is no limit to the number of characters an identifier can contain. n You can't use a Java keyword as an identifier. Table 1-1 lists all of the Java keywords including one new one for 5.0, enum. n Identifiers in Java are case-sensitive; foo and FOO are two different identifiers. Examples of legal and illegal identifiers follow, first some legal identifiers: int _a; int $c; int ______2_w; int _$; int this_is_a_very_detailed_name_for_an_identifier; The following are illegal (it's your job to recognize why): int :b; int -d; int e#; int .f; int 7g; Legal Identifiers (Exam Objectives 1.3 and 1.4) abstract boolean break byte case catch char class const continue default do double else extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while assert enum Sun's Java Code Conventions Sun estimates that over the lifetime of a standard piece of code, 20 percent of the effort will go into the original creation and testing of the code, and 80 percent of the effort will go into the subsequent maintenance and enhancement of the code. Agreeing on, and coding to, a set of code standards helps to reduce the effort involved in testing, maintaining, and enhancing any piece of code. Sun has created a set of coding standards for Java, and published those standards in a document cleverly titled "Java Code Conventions," which you can find at java.sun.com. It's a great document, short and easy to read and we recommend it highly. That said, you'll find that many of the questions in the exam don't follow the code conventions, because of the limitations in the test engine that is used to deliver the exam internationally. One of the great things about the Sun certifications is that the exams are administered uniformly throughout the world. In order to achieve that, the code listings that you'll see in the real exam are often quite cramped, and do not follow Sun's code standards. In order to toughen you up for the exam, we'll often present code listings that have a similarly cramped look and feel, often indenting our code only two spaces as opposed to the Sun standard of four. We'll also jam our curly braces together unnaturally, and sometimes put several statements on the same line…ouch! For example: 1. class Wombat implements Runnable { 2. private int i; 3. public synchronized void run() { 4. if (i%5 != 0) { i++; } 5. for(int x=0; x<5; x++, i++) Chapter 1: Declarations and Access Control table 1-1 Complete List of Java Keywords (assert added in 1.4, enum added in 1.5) 6. { if (x > 1) Thread.yield(); } 7. System.out.print(i + " "); 8. } 9. public static void main(String[] args) { 10. Wombat n = new Wombat(); 11. for(int x=100; x>0; --x) { new Thread(n).start(); } 12. } } Consider yourself forewarned—you'll see lots of code listings, mock questions, and real exam questions that are this sick and twisted. Nobody wants you to write your code like this. Not your employer, not your coworkers, not us, not Sun, and not the exam creation team! Code like this was created only so that complex concepts could be tested within a universal testing tool. The one standard that is followed as much as possible in the real exam are the naming standards. Here are the naming standards that Sun recommends, and that we use in the exam and in most of the book: n Classes and interfaces The first letter should be capitalized, and if several words are linked together to form the name, the first letter of the inner words should be uppercase (a format that's sometimes called "camelCase"). For classes, the names should typically be nouns. For example: Dog Account PrintWriter For interfaces, the names should typically be adjectives like Runnable Serializable n Methods The first letter should be lowercase, and then normal camelCase rules should be used. In addition, the names should typically be verb-noun pairs. For example: getBalance doCalculation setCustomerName Sun’s Java Code Conventions (Exam Objectives 1.3 and 1.4) n Variables Like methods, the camelCase format should be used, starting with a lowercase letter. Sun recommends short, meaningful names, which sounds good to us. Some examples: buttonWidth accountBalance myString n Constants Java constants are created by marking variables static and final. They should be named using uppercase letters with underscore characters as separators: MIN_HEIGHT JavaBeans Standards The JavaBeans spec is intended to help Java developers create Java components that can be easily used by other Java developers in a visual Integrated Development Environment (IDE) tool (like Eclipse or NetBeans). As a Java programmer, you want to be able to use components from the Java API, but it would be great if you could also buy the Java component you want from "Beans 'R Us," that software company down the street. And once you've found the components, you'd like to be able to access them through a development tool in such a way that you don't have to write all your code from scratch. By using naming rules, the JavaBeans spec helps guarantee that tools can recognize and use components built by different developers. The JavaBeans API is quite involved, but you'll need to study only a few basics for the exam. First, JavaBeans are Java classes that have properties. For our purposes, think of properties as private instance variables. Since they're private, the only way they can be accessed from outside of their class is through methods in the class. The methods that change a property's value are called setter methods, and the methods that retrieve a property's value are called getter methods. The JavaBean naming rules that you'll need to know for the exam are the following: JavaBean Property Naming Rules n If the property is not a boolean, the getter method's prefix must be get. For example, getSize()is a valid JavaBeans getter name for a property named "size." Keep in mind that you do not need to have a variable named size Chapter 1: Declarations and Access Control (although some IDEs expect it). The name of the property is inferred from the getters and setters, not through any variables in your class. What you return from getSize() is up to you. n If the property is a boolean, the getter method's prefix is either get or is. For example, getStopped() or isStopped() are both valid JavaBeans names for a boolean property. n The setter method's prefix must be set. For example, setSize() is the valid JavaBean name for a property named size. n To complete the name of a getter or setter method, change the first letter of the property name to uppercase, and then append it to the appropriate prefix (get, is, or set). n Setter method signatures must be marked public, with a void return type and an argument that represents the property type. n Getter method signatures must be marked public, take no arguments, and have a return type that matches the argument type of the setter method for that property. Second, the JavaBean spec supports events, which allow components to notify each other when something happens. The event model is often used in GUI applications when an event like a mouse click is multicast to many other objects that may have things to do when the mouse click occurs. The objects that receive the information that an event occurred are called listeners. For the exam, you need to know that the methods that are used to add or remove listeners from an event must also follow JavaBean naming standards: JavaBean Listener Naming Rules n Listener method names used to "register" a listener with an event source must use the prefix add, followed by the listener type. For example, addActionListener() is a valid name for a method that an event source will have to allow others to register for Action events. n Listener method names used to remove ("unregister") a listener must use the prefix remove, followed by the listener type (using the same rules as the registration add method). n The type of listener to be added or removed must be passed as the argument to the method. JavaBeans Standards (Exam Objectives 1.3 and 1.4) Examples of valid JavaBean method signatures are public void setMyValue(int v) public int getMyValue() public boolean isMyStatus() public void addMyListener(MyListener m) public void removeMyListener(MyListener m) Examples of invalid JavaBean method signatures are void setCustomerName(String s) // must be public public void modifyMyValue(int v) // can't use 'modify' public void addXListener(MyListener m) // listener type mismatch Certification Objective Declare Classes (Exam Objective 1.1) 1.1 Develop code that declares classes (including abstract and all forms of nested classes), interfaces, and enums, and includes the appropriate use of package and import statements (including static imports). 10 Chapter 1: Declarations and Access Control The objective says you have to know legal identifiers only for variable names, but the rules are the same for ALL Java components. So remember that a legal identifier for a variable is also a legal identifier for a method or a class. However, you need to distinguish between legal identifiers and naming conventions, such as the JavaBeans standards, that indicate how a Java component should be named. In other words, you must be able to recognize that an identifier is legal even if it doesn’t conform to naming standards. If the exam question is asking about naming conventions—not just whether an identifier will compile—JavaBeans will be mentioned explicitly. When you write code in Java, you're writing classes or interfaces. Within those classes, as you know, are variables and methods (plus a few other things). How you declare your classes, methods, and variables dramatically affects your code's behavior. For example, a public method can be accessed from code running anywhere in your application. Mark that method private, though, and it vanishes from everyone's radar (except the class in which it was declared). For this objective, we'll study the ways in which you can declare and modify (or not) a class. You'll find that we cover modifiers in an extreme level of detail, and though we know you're already familiar with them, we're starting from the very beginning. Most Java programmers think they know how all the modifiers work, but on closer study often find out that they don't (at least not to the degree needed for the exam). Subtle distinctions are everywhere, so you need to be absolutely certain you're completely solid on everything in this section's objectives before taking the exam. Source File Declaration Rules Before we dig into class declarations, let's do a quick review of the rules associated with declaring classes, import statements, and package statements in a source file: n There can be only one public class per source code file. n Comments can appear at the beginning or end of any line in the source code file; they are independent of any of the positioning rules discussed here. n If there is a public class in a file, the name of the file must match the name of the public class. For example, a class declared as public class Dog { } must be in a source code file named Dog.java. n If the class is part of a package, the package statement must be the first line in the source code file, before any import statements that may be present. n If there are import statements, they must go between the package statement (if there is one) and the class declaration. If there isn't a package statement, then the import statement(s) must be the first line(s) in the source code file. If there are no package or import statements, the class declaration must be the first line in the source code file. n import and package statements apply to all classes within a source code file. In other words, there's no way to declare multiple classes in a file and have them in different packages, or use different imports. n A file can have more than one nonpublic class. Source File Declaration Rules (Exam Objective 1.1) 11 n Files with no public classes can have a name that does not match any of the classes in the file. In Chapter 10 we'll go into a lot more detail about the rules involved with declaring and using imports, packages, and a feature new to Java 5, static imports. Class Declarations and Modifiers Although nested (often called inner) classes are on the exam, we'll save nested class declarations for Chapter 8. You're going to love that chapter. No, really. Seriously. The following code is a bare-bones class declaration: class MyClass { } This code compiles just fine, but you can also add modifiers before the class declaration. Modifiers fall into two categories: n Access modifiers: public, protected, private. n Non-access modifiers (including strictfp, final, and abstract). We'll look at access modifiers first, so you'll learn how to restrict or allow access to a class you create. Access control in Java is a little tricky because there are four access controls (levels of access) but only three access modifiers. The fourth access control level (called default or package access) is what you get when you don't use any of the three access modifiers. In other words, every class, method, and instance variable you declare has an access control, whether you explicitly type one or not. Although all four access controls (which means all three modifiers) work for most method and variable declarations, a class can be declared with only public or default access; the other two access control levels don't make sense for a class, as you'll see. Java is a package-centric language; the developers assumed that for good organization and name scoping, you would put all your classes into packages. They were right, and you should. Imagine this nightmare: Three different programmers, in the same company but working on different parts of a project, write a class named Utilities. If those three Utilities classes have 12 Chapter 1: Declarations and Access Control not been declared in any explicit package, and are in the classpath, you won't have any way to tell the compiler or JVM which of the three you're trying to reference. Sun recommends that developers use reverse domain names, appended with division and/or project names. For example, if your domain name is geeksanonymous.com, and you're working on the client code for the TwelvePointOSteps program, you would name your package something like com.geeksanonymous.steps.client. That would essentially change the name of your class to com.geeksanonymous.steps.client.Utilities. You might still have name collisions within your company, if you don't come up with your own naming schemes, but you're guaranteed not to collide with classes developed outside your company (assuming they follow Sun's naming convention, and if they don't, well, Really Bad Things could happen). Class Access What does it mean to access a class? When we say code from one class (class A) has access to another class (class B), it means class A can do one of three things: n Create an instance of class B. n Extend class B (in other words, become a subclass of class B). n Access certain methods and variables within class B, depending on the access control of those methods and variables. In effect, access means visibility. If class A can't see class B, the access level of the methods and variables within class B won't matter; class A won't have any way to access those methods and variables. Default Access A class with default access has no modifier preceding it in the declaration! It's the access control you get when you don't type a modifier in the class declaration. Think of default access as package-level access, because a class with default access can be seen only by classes within the same package. For example, if class A and class B are in different packages, and class A has default access, class B won't be able to create an instance of class A, or even declare a variable or return type of class A. In fact, class B has to pretend that class A doesn't even exist, or the compiler will complain. Look at the following source file: Class Declarations and Modifiers (Exam Objective 1.1) 13 package cert; class Beverage { } Now look at the second source file: package exam.stuff; import cert.Beverage; class Tea extends Beverage { } As you can see, the superclass (Beverage) is in a different package from the subclass (Tea). The import statement at the top of the Tea file is trying (fingers crossed) to import the Beverage class. The Beverage file compiles fine, but when we try to compile the Tea file we get something like: Can't access class cert.Beverage. Class or interface must be public, in same package, or an accessible member class. import cert.Beverage; Tea won't compile because its superclass, Beverage, has default access and is in a different package. Apart from using fully qualified class names, which we'll cover in Chapter 10, you can do one of two things to make this work. You could put both classes in the same package, or you could declare Beverage as public, as the next section describes. When you see a question with complex logic, be sure to look at the access modifiers first. That way, if you spot an access violation (for example, a class in package A trying to access a default class in package B), you'll know the code won't compile so you don't have to bother working through the logic. It's not as if you don't have anything better to do with your time while taking the exam. Just choose the "Compilation fails" answer and zoom on to the next question. Public Access A class declaration with the public keyword gives all classes from all packages access to the public class. In other words, all classes in the Java Universe (JU) have access to a public class. Don't forget, though, that if a public class you're trying to use is in a different package from the class you're writing, you'll still need to import the public class. In the example from the preceding section, we may not want to place the subclass in the same package as the superclass. To make the code work, we need to add the keyword public in front of the superclass (Beverage) declaration, as follows: 14 Chapter 1: Declarations and Access Control Class Declarations and Modifiers (Exam Objective 1.1) 15 package cert; public class Beverage { } This changes the Beverage class so it will be visible to all classes in all packages. The class can now be instantiated from all other classes, and any class is now free to subclass (extend from) it—unless, that is, the class is also marked with the nonaccess modifier final. Read on. Other (Nonaccess) Class Modifiers You can modify a class declaration using the keyword final, abstract, or strictfp. These modifiers are in addition to whatever access control is on the class, so you could, for example, declare a class as both public and final. But you can't always mix nonaccess modifiers. You're free to use strictfp in combination with final, for example, but you must never, ever, ever mark a class as both final and abstract. You'll see why in the next two sections. You won't need to know how strictfp works, so we're focusing only on modifying a class as final or abstract. For the exam, you need to know only that strictfp is a keyword and can be used to modify a class or a method, but never a variable. Marking a class as strictfp means that any method code in the class will conform to the IEEE 754 standard rules for floating points. Without that modifier, floating points used in the methods might behave in a platform-dependent way. If you don't declare a class as strictfp, you can still get strictfp behavior on a method-by-method basis, by declaring a method as strictfp. If you don't know the IEEE 754 standard, now's not the time to learn it. You have, as we say, bigger fish to fry. Final Classes When used in a class declaration, the final keyword means the class can't be subclassed. In other words, no other class can ever extend (inherit from) a final class, and any attempts to do so will give you a compiler error. So why would you ever mark a class final? After all, doesn't that violate the whole object-oriented (OO) notion of inheritance? You should make a final class only if you need an absolute guarantee that none of the methods in that class will ever be overridden. If you're deeply dependent on the implementations of certain methods, then using final gives you the security that nobody can change the implementation out from under you. You'll notice many classes in the Java core libraries are final. For example, the String class cannot be subclassed. Imagine the havoc if you couldn't guarantee how a String object would work on any given system your application is running on! If programmers were free to extend the String class (and thus substitute their new String subclass instances where java.lang.String instances are expected), civilization—as we know it—could collapse. So use final for safety, but only when you're certain that your final class has indeed said all that ever needs to be said in its methods. Marking a class final means, in essence, your class can't ever be improved upon, or even specialized, by another programmer. A benefit of having nonfinal classes is this scenario: Imagine you find a problem with a method in a class you're using, but you don't have the source code. So you can't modify the source to improve the method, but you can extend the class and override the method in your new subclass, and substitute the subclass everywhere the original superclass is expected. If the class is final, though, then you're stuck. Let's modify our Beverage example by placing the keyword final in the declaration: package cert; public final class Beverage { public void importantMethod() { } } Now, if we try to compile the Tea subclass: package exam.stuff; import cert.Beverage; class Tea extends Beverage { } We get an error something like Can't subclass final classes: class cert.Beverage class Tea extends Beverage{ 1 error In practice, you'll almost never make a final class. A final class obliterates a key benefit of OO—extensibility. So unless you have a serious safety or security issue, assume that some day another programmer will need to extend your class. If you don't, the next programmer forced to maintain your code will hunt you down and <insert really scary thing>. Abstract Classes An abstract class can never be instantiated. Its sole purpose, mission in life, raison d'être, is to be extended (subclassed). (Note, however, that you can compile and execute an abstract class, as long as you don't try 16 Chapter 1: Declarations and Access Control Class Declarations and Modifiers (Exam Objective 1.1) 17 to make an instance of it.) Why make a class if you can't make objects out of it? Because the class might be just too, well, abstract. For example, imagine you have a class Car that has generic methods common to all vehicles. But you don't want anyone actually creating a generic, abstract Car object. How would they initialize its state? What color would it be? How many seats? Horsepower? All-wheel drive? Or more importantly, how would it behave? In other words, how would the methods be implemented? No, you need programmers to instantiate actual car types such as BMWBoxster and SubaruOutback. We'll bet the Boxster owner will tell you his car does things the Subaru can do "only in its dreams." Take a look at the following abstract class: abstract class Car { private double price; private String model; private String year; public abstract void goFast(); public abstract void goUpHill(); public abstract void impressNeighbors(); // Additional, important, and serious code goes here } The preceding code will compile fine. However, if you try to instantiate a Car in another body of code, you'll get a compiler error something like this: AnotherClass.java:7: class Car is an abstract class. It can't be instantiated. Car x = new Car(); 1 error Notice that the methods marked abstract end in a semicolon rather than curly braces. Look for questions with a method declaration that ends with a semicolon, rather than curly braces. If the method is in a class—as opposed to an interface—then both the method and the class must be marked abstract. You might get a question that asks how you could fix a code sample that includes a method ending in a semicolon, but without an abstract modifier on the class or method. In that case, you could either mark the method and class abstract, or change the semicolon to code (like a curly brace pair). Remember, if you change a method from abstract to nonabstract, don't forget to change the semicolon at the end of the method declaration into a curly brace pair! We'll look at abstract methods in more detail later in this objective, but always remember that if even a single method is abstract, the whole class must be declared abstract. One abstract method spoils the whole bunch. You can, however, put nonabstract methods in an abstract class. For example, you might have methods with implementations that shouldn't change from Car type to Car type, such as getColor() or setPrice(). By putting nonabstract methods in an abstract class, you give all concrete subclasses (concrete just means not abstract) inherited method implementations. The good news there is that concrete subclasses get to inherit functionality, and need to implement only the methods that define subclassspecific behavior. (By the way, if you think we misused raison d'être earlier, don't send an e-mail. We'd like to see you work it into a programmer certification book.) Coding with abstract class types (including interfaces, discussed later in this chapter) lets you take advantage of polymorphism, and gives you the greatest degree of flexibility and extensibility. You'll learn more about polymorphism in Chapter 2. You can't mark a class as both abstract and final. They have nearly opposite meanings. An abstract class must be subclassed, whereas a final class must not be subclassed. If you see this combination of abstract and final modifiers, used for a class or method declaration, the code will not compile. Exercise 1-1 Creating an Abstract Superclass and Concrete Subclass The following exercise will test your knowledge of public, default, final, and abstract classes. Create an abstract superclass named Fruit and a concrete subclass named Apple. The superclass should belong to a package called food and the subclass can belong to the default package (meaning it isn't put into a package explicitly). Make the superclass public and give the subclass default access. 1. Create the superclass as follows: package food; public abstract class Fruit{ /* any code you want */} 2. Create the subclass in a separate file as follows: import food.Fruit; class Apple extends Fruit{ /* any code you want */} 18 Chapter 1: Declarations and Access Control 3. Create a directory called food off the directory in your class path setting. 4. Attempt to compile the two files. If you want to use the Apple class, make sure you place the Fruit.class file in the food subdirectory. Certification Objective Declare Interfaces (Exam Objectives 1.1 and 1.2) 1.1 Develop code that declares classes (including abstract and all forms of nested classes), interfaces, and enums, and includes the appropriate use of package and import statements (including static imports). 1.2 Develop code that declares an interface. Develop code that implements or extends one or more interfaces. Develop code that declares an abstract class. Develop code that extends an abstract class. Declaring an Interface When you create an interface, you're defining a contract for what a class can do, without saying anything about how the class will do it. An interface is a contract. You could write an interface Bounceable, for example, that says in effect, "This is the Bounceable interface. Any class type that implements this interface must agree to write the code for the bounce() and setBounceFactor() methods." By defining an interface for Bounceable, any class that wants to be treated as a Bounceable thing can simply implement the Bounceable interface and provide code for the interface's two methods. Interfaces can be implemented by any class, from any inheritance tree. This lets you take radically different classes and give them a common characteristic. For example, you might want both a Ball and a Tire to have bounce behavior, but Ball and Tire don't share any inheritance relationship; Ball extends Toy while Tire extends only java.lang.Object. But by making both Ball and Tire implement Bounceable, you're saying that Ball and Tire can be treated as, "Things that can bounce," which in Java translates to "Things on which you can invoke the Declaring an Interface (Exam Objectives 1.1 and 1.2) 19 bounce() and setBounceFactor() methods." Figure 1-1 illustrates the relationship between interfaces and classes. FIGURE 1-1 The Relationship between interfaces and classes Think of an interface as a 100-percent abstract class. Like an abstract class, an interface defines abstract methods that take the following form: abstract void bounce(); // Ends with a semicolon rather than // curly braces But while an abstract class can define both abstract and non-abstract methods, an interface can have only abstract methods. Another way interfaces differ from abstract classes is that interfaces have very little flexibility in how the methods and variables defined in the interface are declared. These rules are strict: n All interface methods are implicitly public and abstract. In other words, you do not need to actually type the public or abstract modifiers in the method declaration, but the method is still always public and abstract. n All variables defined in an interface must be public, static, and final— in other words, interfaces can declare only constants, not instance variables. 20 Chapter 1: Declarations and Access Control n Interface methods must not be static. n Because interface methods are abstract, they cannot be marked final, strictfp, or native. (More on these modifiers later.) n An interface can extend one or more other interfaces. n An interface cannot extend anything but another interface. n An interface cannot implement another interface or class. n An interface must be declared with the keyword interface. n Interface types can be used polymorphically (see Chapter 2 for more details). The following is a legal interface declaration: public abstract interface Rollable { } Typing in the abstract modifier is considered redundant; interfaces are implicitly abstract whether you type abstract or not. You just need to know that both of these declarations are legal, and functionally identical: public abstract interface Rollable { } public interface Rollable { } The public modifier is required if you want the interface to have public rather than default access. We've looked at the interface declaration but now we'll look closely at the methods within an interface: public interface Bounceable { public abstract void bounce(); public abstract void setBounceFactor(int bf); } Typing in the public and abstract modifiers on the methods is redundant, though, since all interface methods are implicitly public and abstract. Given that rule, you can see that the following code is exactly equivalent to the preceding interface: public interface Bounceable { void bounce(); // No modifiers void setBounceFactor(int bf); // No modifiers } Declaring an Interface (Exam Objectives 1.1 and 1.2) 21 You must remember that all interface methods are public and abstract regardless of what you see in the interface definition. Look for interface methods declared with any combination of public, abstract, or no modifiers. For example, the following five method declarations, if declared within their own interfaces, are legal and identical! void bounce(); public void bounce(); abstract void bounce(); public abstract void bounce(); abstract public void bounce(); The following interface method declarations won't compile: final void bounce(); // final and abstract can never be used // together, and abstract is implied static void bounce(); // interfaces define instance methods private void bounce(); // interface methods are always public protected void bounce(); // (same as above) Declaring Interface Constants You're allowed to put constants in an interface. By doing so, you guarantee that any class implementing the interface will have access to the same constant. By placing the constants right in the interface, any class that implements the interface has direct access to the constants, just as if the class had inherited them. You need to remember one key rule for interface constants. They must always be public static final So that sounds simple, right? After all, interface constants are no different from any other publicly accessible constants, so they obviously must be declared public, static, and final. But before you breeze past the rest of this discussion, think about the implications: Because interface constants are defined in an interface, they don't have to be declared as public, static, or final. They must be public, static, and final, but you don't have to actually declare them that way. Just as interface methods are always public and abstract whether you say so in the code or not, any variable defined in an interface must be—and implicitly is—a public 22 Chapter 1: Declarations and Access Control constant. See if you can spot the problem with the following code (assume two separate files): interface Foo { int BAR = 42; void go(); } class Zap implements Foo { public void go() { BAR = 27; } } You can't change the value of a constant! Once the value has been assigned, the value can never be modified. The assignment happens in the interface itself (where the constant is declared), so the implementing class can access it and use it, but as a read-only value. So the BAR = 27 assignment will not compile. Declaring Interface Constants (Exam Objectives 1.1 and 1.2) 23 Look for interface definitions that define constants, but without explicitly using the required modifiers. For example, the following are all identical: public int x = 1; // Looks non-static and non-final, // but isn't! int x = 1; // Looks default, non-final, // non-static, but isn't! static int x = 1; // Doesn't show final or public final int x = 1; // Doesn't show static or public public static int x = 1; // Doesn't show final public final int x = 1; // Doesn't show static static final int x = 1 // Doesn't show public public static final int x = 1; // what you get implicitly Any combination of the required (but implicit) modifiers is legal, as is using no modifiers at all! On the exam, you can expect to see questions you won’t be able to answer correctly unless you know, for example, that an interface variable is final and can never be given a value by the implementing (or any other) class. Certification Objective Declare Class Members (Objectives 1.3 and 1.4) 1.3 Develop code that declares, initializes, and uses primitives, arrays, enums, and objects as static, instance, and local variables. Also, use legal identifiers for variable names. 1.4 Develop code that declares both static and non-static methods, and—if appropriate—use method names that adhere to the JavaBeans naming standards. Also develop code that declares and uses a variable-length argument list. We've looked at what it means to use a modifier in a class declaration, and now we'll look at what it means to modify a method or variable declaration. Methods and instance (nonlocal) variables are collectively known as members. You can modify a member with both access and nonaccess modifiers, and you have more modifiers to choose from (and combine) than when you're declaring a class. Access Modifiers Because method and variable members are usually given access control in exactly the same way, we'll cover both in this section. Whereas a class can use just two of the four access control levels (default or public), members can use all four: n public n protected n default n private Default protection is what you get when you don't type an access modifier in the member declaration. The default and protected access control types have almost identical behavior, except for one difference that will be mentioned later. It's crucial that you know access control inside and out for the exam. There will be quite a few questions with access control playing a role. Some questions test 24 Chapter 1: Declarations and Access Control several concepts of access control at the same time, so not knowing one small part of access control could blow an entire question. What does it mean for code in one class to have access to a member of another class? For now, ignore any differences between methods and variables. If class A has access to a member of class B, it means that class B's member is visible to class A. When a class does not have access to another member, the compiler will slap you for trying to access something that you're not even supposed to know exists! You need to understand two different access issues: n Whether method code in one class can access a member of another class n Whether a subclass can inherit a member of its superclass The first type of access is when a method in one class tries to access a method or a variable of another class, using the dot operator (.) to invoke a method or retrieve a variable. For example: class Zoo { public String coolMethod() { return "Wow baby"; } } class Moo { public void useAZoo() { Zoo z = new Zoo(); // If the preceding line compiles Moo has access // to the Zoo class // But... does it have access to the coolMethod()? System.out.println("A Zoo says, " + z.coolMethod()); // The preceding line works because Moo can access the // public method } } The second type of access revolves around which, if any, members of a superclass a subclass can access through inheritance. We're not looking at whether the subclass can, say, invoke a method on an instance of the superclass (which would just be an example of the first type of access). Instead, we're looking at whether the subclass inherits a member of its superclass. Remember, if a subclass inherits a member, it's exactly as if the subclass actually declared the member itself. In other words, if a subclass inherits a member, the subclass has the member. Access Modifiers (Exam Objectives 1.3 and 1.4) 25 class Zoo { public String coolMethod() { return "Wow baby"; } } class Moo extends Zoo { public void useMyCoolMethod() { // Does an instance of Moo inherit the coolMethod()? System.out.println("Moo says, " + this.coolMethod()); // The preceding line works because Moo can inherit the // public method // Can an instance of Moo invoke coolMethod() on an // instance of Zoo? Zoo z = new Zoo(); System.out.println("Zoo says, " + z.coolMethod()); // coolMethod() is public, so Moo can invoke it on a Zoo //reference } } Figure 1-2 compares a class inheriting a member of another class, and accessing a member of another class using a reference of an instance of that class. Much of access control (both types) centers on whether the two classes involved are in the same or different packages. Don't forget, though, if class A itself can't be accessed by class B, then no members within class A can be accessed by class B. You need to know the effect of different combinations of class and member access (such as a default class with a public variable). To figure this out, first look at the access level of the class. If the class itself will not be visible to another class, then none of the members will be either, even if the member is declared public. Once you've confirmed that the class is visible, then it makes sense to look at access levels on individual members. Public Members When a method or variable member is declared public, it means all other classes, regardless of the package they belong to, can access the member (assuming the class itself is visible). 26 Chapter 1: Declarations and Access Control FIGURE 1-2 Comparison of inheritance vs. dot operator for member access. Access Modifiers (Exam Objectives 1.3 and 1.4) 27 28 Chapter 1: Declarations and Access Control Look at the following source file: package book; import cert.*; // Import all classes in the cert package class Goo { public static void main(String[] args) { Sludge o = new Sludge(); o.testIt(); } } Now look at the second file: package cert; public class Sludge { public void testIt() { System.out.println("sludge"); } } As you can see, Goo and Sludge are in different packages. However, Goo can invoke the method in Sludge without problems because both the Sludge class and its testIt() method are marked public. For a subclass, if a member of its superclass is declared public, the subclass inherits that member regardless of whether both classes are in the same package: package cert; public class Roo { public String doRooThings() { // imagine the fun code that goes here return "fun"; } } The Roo class declares the doRooThings() member as public. So if we make a subclass of Roo, any code in that Roo subclass can call its own inherited doRooThings() method. package notcert; //Not the package Roo is in import cert.Roo; class Cloo extends Roo { public void testCloo() { System.out.println(doRooThings()); } } Notice in the preceding code that the doRooThings() method is invoked without having to preface it with a reference. Remember, if you see a method invoked (or a variable accessed) without the dot operator (.), it means the method or variable belongs to the class where you see that code. It also means that the method or variable is implicitly being accessed using the this reference. So in the preceding code, the call to doRooThings() in the Cloo class could also have been written as this.doRooThings(). The reference this always refers to the currently executing object—in other words, the object running the code where you see the this reference. Because the this reference is implicit, you don't need to preface your member access code with it, but it won't hurt. Some programmers include it to make the code easier to read for new (or non) Java programmers. Besides being able to invoke the doRooThings() method on itself, code from some other class can call doRooThings() on a Cloo instance, as in the following: class Toon { public static void main(String[] args) { Cloo c = new Cloo(); System.out.println(c.doRooThings()); //No problem; method // is public } } Private Members Members marked private can't be accessed by code in any class other than the class in which the private member was declared. Let's make a small change to the Roo class from an earlier example. package cert; public class Roo { private String doRooThings() { // imagine the fun code that goes here, but only the Roo // class knows return "fun"; } } The doRooThings() method is now private, so no other class can use it. If we try to invoke the method from any other class, we'll run into trouble: Access Modifiers (Exam Objectives 1.3 and 1.4) 29 30 Chapter 1: Declarations and Access Control package notcert; import cert.Roo; class UseARoo { public void testIt() { Roo r = new Roo(); //So far so good; class Roo is public System.out.println(r.doRooThings()); //Compiler error! } } If we try to compile UseARoo, we get a compiler error something like this: cannot find symbol symbol : method doRooThings() It's as if the method doRooThings() doesn't exist, and as far as any code outside of the Roo class is concerned, it's true. A private member is invisible to any code outside the member's own class. What about a subclass that tries to inherit a private member of its superclass? When a member is declared private, a subclass can't inherit it. For the exam, you need to recognize that a subclass can't see, use, or even think about the private members of its superclass. You can, however, declare a matching method in the subclass. But regardless of how it looks, it is not an overriding method! It is simply a method that happens to have the same name as a private method (which you're not supposed to know about) in the superclass. The rules of overriding do not apply, so you can make this newly-declared-but-just-happens-to-match method declare new exceptions, or change the return type, or anything else you want to do with it. package cert; public class Roo { private String doRooThings() { // imagine the fun code that goes here, but no other class // will know return "fun"; } } The doRooThings() method is now off limits to all subclasses, even those in the same package as the superclass: package cert; //Cloo and Roo are in the same package class Cloo extends Roo { //Still OK, superclass Roo is public public void testCloo() { System.out.println(doRooThings()); //Compiler error! } } If we try to compile the subclass Cloo, the compiler is delighted to spit out an error something like this: %javac Cloo.java Cloo.java:4: Undefined method: doRooThings() System.out.println(doRooThings()); 1 error Although you're allowed to mark instance variables as public, in practice it's nearly always best to keep all variables private or protected. If variables need to be changed, set, or read, programmers should use public accessor methods, so that code in any other class has to ask to get or set a variable (by going through a method), rather than access it directly. JavaBean-compliant accessor methods take the form get<propertyName> or, for booleans, is<propertyName> and set<propertyName>, and provide a place to check and/or validate before returning or modifying a value. Without this protection, the weight variable of a Cat object, for example, could be set to a negative number if the offending code goes straight to the public variable as in someCat.weight = -20. But an accessor method, setWeight(int wt), could check for an inappropriate number. (OK, wild speculation, but we're guessing a negative weight might be inappropriate for a cat. Or not.) Chapter 2 will discuss this data protection (encapsulation) in more detail. Can a private method be overridden by a subclass? That's an interesting question, but the answer is technically no. Since the subclass, as we've seen, cannot inherit a private method, it therefore cannot override the method—overriding depends on inheritance. We'll cover the implications of this in more detail a little later in this section as well as in Chapter 2, but for now just remember that a method marked private cannot be overridden. Figure 1-3 illustrates the effects of the public and private modifiers on classes from the same or different packages. Access Modifiers (Exam Objectives 1.3 and 1.4) 31 32 Chapter 1: Declarations and Access Control FIGURE 1-3 Effects of public and private access Protected and Default Members The protected and default access control levels are almost identical, but with one critical difference. A default member may be accessed only if the class accessing the member belongs to the same package, whereas a protected member can be accessed (through inheritance) by a subclass even if the subclass is in a different package. Take a look at the following two classes: package certification; public class OtherClass { void testIt() { // No modifier means method has default // access System.out.println("OtherClass"); } } In another source code file you have the following: package somethingElse; import certification.OtherClass; class AccessClass { static public void main(String[] args) { OtherClass o = new OtherClass(); o.testIt(); } } As you can see, the testIt() method in the first file has default (think: packagelevel) access. Notice also that class OtherClass is in a different package from the AccessClass. Will AccessClass be able to use the method testIt()? Will it cause a compiler error? Will Daniel ever marry Francesca? Stay tuned. No method matching testIt() found in class certification.OtherClass. o.testIt(); From the preceding results, you can see that AccessClass can't use the OtherClass method testIt() because testIt() has default access, and AccessClass is not in the same package as OtherClass. So AccessClass can't see it, the compiler complains, and we have no idea who Daniel and Francesca are. Default and protected behavior differ only when we talk about subclasses. If the protected keyword is used to define a member, any subclass of the class declaring the member can access it through inheritance. It doesn't matter if the superclass and subclass are in different packages, the protected superclass member is still visible to the subclass (although visible only in a very specific way as we'll see a little later). This is in contrast to the default behavior, which doesn't allow a subclass to access a superclass member unless the subclass is in the same package as the superclass. Access Modifiers (Exam Objectives 1.3 and 1.4) 33 34 Chapter 1: Declarations and Access Control Whereas default access doesn't extend any special consideration to subclasses (you're either in the package or you're not), the protected modifier respects the parent-child relationship, even when the child class moves away (and joins a new package). So, when you think of default access, think package restriction. No exceptions. But when you think protected, think package + kids. A class with a protected member is marking that member as having package-level access for all classes, but with a special exception for subclasses outside the package. But what does it mean for a subclass-outside-the-package to have access to a superclass (parent) member? It means the subclass inherits the member. It does not, however, mean the subclass-outside-the-package can access the member using a reference to an instance of the superclass. In other words, protected = inheritance. Protected does not mean that the subclass can treat the protected superclass member as though it were public. So if the subclass-outside-the-package gets a reference to the superclass (by, for example, creating an instance of the superclass somewhere in the subclass' code), the subclass cannot use the dot operator on the superclass reference to access the protected member. To a subclass-outside-the-package, a protected member might as well be default (or even private), when the subclass is using a reference to the superclass. The subclass can see the protected member only through inheritance. Are you confused? So are we. Hang in there and it will all become clear with the next batch of code examples. (And don't worry; we're not actually confused. We're just trying to make you feel better if you are. You know, like it's OK for you to feel as though nothing makes sense, and that it isn't your fault. Or is it? <insert evil laugh>) Protected Details Let's take a look at a protected instance variable (remember, an instance variable is a member) of a superclass. package certification; public class Parent { protected int x = 9; // protected access } The preceding code declares the variable x as protected. This makes the variable accessible to all other classes inside the certification package, as well as inheritable by any subclasses outside the package. Now let's create a subclass in a different package, and attempt to use the variable x (that the subclass inherits): package other; // Different package import certification.Parent; class Child extends Parent { public void testIt() { System.out.println("x is " + x); // No problem; Child // inherits x } } The preceding code compiles fine. Notice, though, that the Child class is accessing the protected variable through inheritance. Remember, any time we talk about a subclass having access to a superclass member, we could be talking about the subclass inheriting the member, not simply accessing the member through a reference to an instance of the superclass (the way any other nonsubclass would access it). Watch what happens if the subclass Child (outside the superclass' package) tries to access a protected variable using a Parent class reference. package other; import certification.Parent; class Child extends Parent { public void testIt() { System.out.println("x is " + x); // No problem; Child // inherits x Parent p = new Parent(); // Can we access x using the // p reference? System.out.println("X in parent is " + p.x); // Compiler // error! } } The compiler is more than happy to show us the problem: %javac -d . other/Child.java other/Child.java:9: x has protected access in certification.Parent System.out.println("X in parent is " + p.x); ^ 1 error So far we've established that a protected member has essentially package-level or default access to all classes except for subclasses. We've seen that subclasses outside the package can inherit a protected member. Finally, we've seen that subclasses Access Modifiers (Objectives 1.3 and 1.4) 35 36 Chapter 1: Declarations and Access Control outside the package can't use a superclass reference to access a protected member. For a subclass outside the package, the protected member can be accessed only through inheritance. But there's still one more issue we haven't looked at...what does a protected member look like to other classes trying to use the subclass-outside-the-package to get to the subclass' inherited protected superclass member? For example, using our previous Parent/Child classes, what happens if some other class—Neighbor, say—in the same package as the Child (subclass), has a reference to a Child instance and wants to access the member variable x ? In other words, how does that protected member behave once the subclass has inherited it? Does it maintain its protected status, such that classes in the Child's package can see it? No! Once the subclass-outside-the-package inherits the protected member, that member (as inherited by the subclass) becomes private to any code outside the subclass, with the exception of subclasses of the subclass. So if class Neighbor instantiates a Child object, then even if class Neighbor is in the same package as class Child, class Neighbor won't have access to the Child's inherited (but protected) variable x. The bottom line: when a subclass-outside-the-package inherits a protected member, the member is essentially private inside the subclass, such that only the subclass and its subclasses can access it. Figure 1-4 illustrates the effect of protected access on classes and subclasses in the same or different packages. Whew! That wraps up protected, the most misunderstood modifier in Java. Again, it's used only in very special cases, but you can count on it showing up on the exam. Now that we've covered the protected modifier, we'll switch to default member access, a piece of cake compared to protected. Default Details Let's start with the default behavior of a member in a superclass. We'll modify the Parent's member x to make it default. package certification; public class Parent { int x = 9; // No access modifier, means default // (package) access } Notice we didn't place an access modifier in front of the variable x. Remember that if you don't type an access modifier before a class or member declaration, the access control is default, which means package level. We'll now attempt to access the default member from the Child class that we saw earlier. FIGURE 1-4 Effects of protected access When we compile the child file, we get an error something like this: Child.java:4: Undefined variable: x System.out.println("Variable x is " + x); 1 error Access Modifiers (Exam Objectives 1.3 and 1.4) 37 38 Chapter 1: Declarations and Access Control The compiler gives the same error as when a member is declared as private. The subclass Child (in a different package from the superclass Parent) can't see or use the default superclass member x ! Now, what about default access for two classes in the same package? package certification; public class Parent{ int x = 9; // default access } And in the second class you have the following: package certification; class Child extends Parent{ static public void main(String[] args) { Child sc = new Child(); sc.testIt(); } public void testIt() { System.out.println("Variable x is " + x); // No problem; } } The preceding source file compiles fine, and the class Child runs and displays the value of x. Just remember that default members are visible to subclasses only if those subclasses are in the same package as the superclass. Local Variables and Access Modifiers Can access modifiers be applied to local variables? NO! There is never a case where an access modifier can be applied to a local variable, so watch out for code like the following: class Foo { void doStuff() { private int x = 7; this.doMore(x); } } You can be certain that any local variable declared with an access modifier will not compile. In fact, there is only one modifier that can ever be applied to local variables—final. That about does it for our discussion on member access modifiers. Table 1-2 shows all the combinations of access and visibility; you really should spend some time with it. Next, we're going to dig into the other (nonaccess) modifiers that you can apply to member declarations. Nonaccess Member Modifiers We've discussed member access, which refers to whether code from one class can invoke a method (or access an instance variable) from another class. That still leaves a boatload of other modifiers you can use on member declarations. Two you're already familiar with—final and abstract—because we applied them to class declarations earlier in this chapter. But we still have to take a quick look at transient, synchronized, native, strictfp, and then a long look at the Big One—static. We'll look first at modifiers applied to methods, followed by a look at modifiers applied to instance variables. We'll wrap up this section with a look at how static works when applied to variables and methods. Access Modifiers (Exam Objectives 1.3 and 1.4) 39 Visibility Public Protected Default Private From the same class Yes Yes Yes Yes From any class in the same package Yes Yes Yes No From a subclass in the same package Yes Yes Yes No From a subclass outside the same package Yes Yes, through inheritance No No From any non-subclass class outside the package Yes No No No table 1-2 Determining Access to Class Members 40 Chapter 1: Declarations and Access Control Final Methods The final keyword prevents a method from being overridden in a subclass, and is often used to enforce the API functionality of a method. For example, the Thread class has a method called isAlive() that checks whether a thread is still active. If you extend the Thread class, though, there is really no way that you can correctly implement this method yourself (it uses native code, for one thing), so the designers have made it final. Just as you can't subclass the String class (because we need to be able to trust in the behavior of a String object), you can't override many of the methods in the core class libraries. This can't-be-overridden restriction provides for safety and security, but you should use it with great caution. Preventing a subclass from overriding a method stifles many of the benefits of OO including extensibility through polymorphism. A typical final method declaration looks like this: class SuperClass{ public final void showSample() { System.out.println("One thing."); } } It's legal to extend SuperClass, since the class isn't marked final, but we can't override the final method showSample(), as the following code attempts to do: class SubClass extends SuperClass{ public void showSample() { // Try to override the final // superclass method System.out.println("Another thing."); } } Attempting to compile the preceding code gives us something like this: %javac FinalTest.java FinalTest.java:5: The method void showSample() declared in class SubClass cannot override the final method of the same signature declared in class SuperClass. Final methods cannot be overridden. public void showSample() { } 1 error Final Arguments Method arguments are the variable declarations that appear in between the parentheses in a method declaration. A typical method declaration with multiple arguments looks like this: public Record getRecord(int fileNumber, int recordNumber) {} Method arguments are essentially the same as local variables. In the preceding example, the variables fileNumber and recordNumber will both follow all the rules applied to local variables. This means they can also have the modifier final: public Record getRecord(int fileNumber, final int recNumber) {} In this example, the variable recordNumber is declared as final, which of course means it can't be modified within the method. In this case, "modified" means reassigning a new value to the variable. In other words, a final argument must keep the same value that the parameter had when it was passed into the method. Abstract Methods An abstract method is a method that's been declared (as abstract) but not implemented. In other words, the method contains no functional code. And if you recall from the earlier section "Abstract Classes," an abstract method declaration doesn't even have curly braces for where the implementation code goes, but instead closes with a semicolon. In other words, it has no method body. You mark a method abstract when you want to force subclasses to provide the implementation. For example, if you write an abstract class Car with a method goUpHill(), you might want to force each subtype of Car to define its own goUpHill() behavior, specific to that particular type of car. public abstract void showSample(); Notice that the abstract method ends with a semicolon instead of curly braces. It is illegal to have even a single abstract method in a class that is not explicitly declared abstract! Look at the following illegal class: public class IllegalClass{ public abstract void doIt(); } NonAccess Member Modifiers (Exam Objectives 1.3 and 1.4) 41 42 Chapter 1: Declarations and Access Control The preceding class will produce the following error if you try to compile it: IllegalClass.java:1: class IllegalClass must be declared abstract. It does not define void doIt() from class IllegalClass. public class IllegalClass{ 1 error You can, however, have an abstract class with no abstract methods. The following example will compile fine: public abstract class LegalClass{ void goodMethod() { // lots of real implementation code here } } In the preceding example, goodMethod() is not abstract. Three different clues tell you it's not an abstract method: n The method is not marked abstract. n The method declaration includes curly braces, as opposed to ending in a semicolon. In other words, the method has a method body. n The method provides actual implementation code. Any class that extends an abstract class must implement all abstract methods of the superclass, unless the subclass is also abstract. The rule is this: The first concrete subclass of an abstract class must implement all abstract methods of the superclass. Concrete just means nonabstract, so if you have an abstract class extending another abstract class, the abstract subclass doesn't need to provide implementations for the inherited abstract methods. Sooner or later, though, somebody's going to make a nonabstract subclass (in other words, a class that can be instantiated), and that subclass will have to implement all the abstract methods from up the inheritance tree. The following example demonstrates an inheritance tree with two abstract classes and one concrete class: public abstract class Vehicle { private String type; public abstract void goUpHill(); // Abstract method public String getType() { // Non-abstract method return type; } } public abstract class Car extends Vehicle { public abstract void goUpHill(); // Still abstract public void doCarThings() { // special car code goes here } } public class Mini extends Car { public void goUpHill() { // Mini-specific going uphill code } } So how many methods does class Mini have? Three. It inherits both the getType() and doCarThings() methods, because they're public and concrete (nonabstract). But because goUpHill() is abstract in the superclass Vehicle, and is never implemented in the Car class (so it remains abstract), it means class Mini—as the first concrete class below Vehicle—must implement the goUpHill() method. In other words, class Mini can't pass the buck (of abstract method implementation) to the next class down the inheritance tree, but class Car can, since Car, like Vehicle, is abstract. Figure 1-5 illustrates the effects of the abstract modifier on concrete and abstract subclasses. NonAccess Member Modifiers (Exam Objectives 1.3 and 1.4) 43 44 Chapter 1: Declarations and Access Control FIGURE 1-5 The effects of the abstract modifier on concrete and abstract subclasses Look for concrete classes that don't provide method implementations for abstract methods of the superclass. The following code won't compile: public abstract class A { abstract void foo(); } class B extends A { void foo(int I) { } } Class B won't compile because it doesn't implement the inherited abstract method foo(). Although the foo(int I) method in class B might appear to be an implementation of the superclass' abstract method, it is simply an overloaded method (a method using the same identifier, but different arguments), so it doesn't fulfill the requirements for implementing the superclass' abstract method. We'll look at the differences between overloading and overriding in detail in Chapter 2. A method can never, ever, ever be marked as both abstract and final, or both abstract and private. Think about it—abstract methods must be implemented (which essentially means overridden by a subclass) whereas final and private methods cannot ever be overridden by a subclass. Or to phrase it another way, an abstract designation means the superclass doesn't know anything about how the subclasses should behave in that method, whereas a final designation means the superclass knows everything about how all subclasses (however far down the inheritance tree they may be) should behave in that method. The abstract and final modifiers are virtually opposites. Because private methods cannot even be seen by a subclass (let alone inherited), they too cannot be overridden, so they too cannot be marked abstract. Finally, you need to know that the abstract modifier can never be combined with the static modifier. We'll cover static methods later in this objective, but for now just remember that the following would be illegal: abstract static void doStuff(); And it would give you an error that should be familiar by now: MyClass.java:2: illegal combination of modifiers: abstract and static abstract static void doStuff(); Synchronized Methods The synchronized keyword indicates that a method can be accessed by only one thread at a time. We'll discuss this nearly to death in Chapter 11, but for now all we're concerned with is knowing that the synchronized modifier can be applied only to methods—not variables, not classes, just methods. A typical synchronized declaration looks like this: public synchronized Record retrieveUserInfo(int id) { } You should also know that the synchronized modifier can be matched with any of the four access control levels (which means it can be paired with any of the three access modifier keywords). Nonaccess Member Modifiers (Exam Objectives 1.3 and 1.4) 45 46 Chapter 1: Declarations and Access Control Native Methods The native modifier indicates that a method is implemented in platform-dependent code, often in C. You don't need to know how to use native methods for the exam, other than knowing that native is a modifier (thus a reserved keyword) and that native can be applied only to methods—not classes, not variables, just methods. Note that a native method's body must be a semicolon (;) (like abstract methods), indicating that the implementation is omitted. Strictfp Methods We looked earlier at using strictfp as a class modifier, but even if you don't declare a class as strictfp, you can still declare an individual method as strictfp. Remember, strictfp forces floating points (and any floating-point operations) to adhere to the IEEE 754 standard. With strictfp, you can predict how your floating points will behave regardless of the underlying platform the JVM is running on. The downside is that if the underlying platform is capable of supporting greater precision, a strictfp method won't be able to take advantage of it. You'll want to study the IEEE 754 if you need something to help you fall asleep. For the exam, however, you don't need to know anything about strictfp other than what it's used for, that it can modify a class or method declaration, and that a variable can never be declared strictfp. Methods with Variable Argument Lists (var-args) As of 5.0, Java allows you to create methods that can take a variable number of arguments. Depending on where you look, you might hear this capability referred to as "variable-length argument lists," "variable arguments," "var-args," "varargs," or our personal favorite (from the department of obfuscation), "variable arity parameter." They're all the same thing, and we'll use the term "var-args" from here on out. As a bit of background, we'd like to clarify how we're going to use the terms "argument" and "parameter" throughout this book. n arguments The things you specify between the parentheses when you're invoking a method: doStuff("a", 2); // invoking doStuff, so a & 2 are arguments n parameters The things in the method's signature that indicate what the method must receive when it's invoked: void doStuff(String s, int a) { } // we're expecting two // parameters: String and int We'll cover using var-arg methods more in the next few chapters, for now let's review the declaration rules for var-args: n Var-arg type When you declare a var-arg parameter, you must specify the type of the argument(s) this parameter of your method can receive. (This can be a primitive type or an object type.) n Basic syntax To declare a method using a var-arg parameter, you follow the type with an ellipsis (...), a space, and then the name of the array that will hold the parameters received. n Other parameters It's legal to have other parameters in a method that uses a var-arg. n Var-args limits The var-arg must be the last parameter in the method's signature, and you can have only one var-arg in a method. Let's look at some legal and illegal var-arg declarations: Legal: void doStuff(int... x) { } // expects from 0 to many ints // as parameters void doStuff2(char c, int... x) { } // expects first a char, // then 0 to many ints void doStuff3(Animal... animal) { } // 0 to many Animals Illegal: void doStuff4(int x...) { } // bad syntax void doStuff5(int... x, char... y) { } // too many var-args void doStuff6(String... s, byte b) { } // var-arg must be last Constructor Declarations In Java, objects are constructed. Every time you make a new object, at least one constructor is invoked. Every class has a constructor, although if you don't create one explicitly, the compiler will build one for you. There are tons of rules concerning Constructor Declarations (Exam Objectives 1.3 and 1.4) 47 48 Chapter 1: Declarations and Access Control constructors, and we're saving our detailed discussion for Chapter 2. For now, let's focus on the basic declaration rules. Here's a simple example: class Foo { protected Foo() { } // this is Foo's constructor protected void Foo() { } // this is a badly named, // but legal, method } The first thing to notice is that constructors look an awful lot like methods. A key difference is that a constructor can't ever, ever, ever, have a return type…ever! Constructor declarations can however have all of the normal access modifiers, and they can take arguments (including var-args), just like methods. The other BIG RULE, to understand about constructors is that they must have the same name as the class in which they are declared. Constructors can't be marked static (they are after all associated with object instantiation), they can't be marked final or abstract (because they can't be overridden). Here are some legal and illegal constructor declarations: class Foo2 { // legal constructors Foo2() { } private Foo2(byte b) { } Foo2(int x) { } Foo2(int x, int... y) { } // illegal constructors void Foo2() { } // it's a method, not a constructor Foo() { } // not a method or a constructor Foo2(short s); // looks like an abstract method static Foo2(float f) { } // can't be static final Foo2(long x) { } // can't be final abstract Foo2(char c) { } // can't be abstract Foo2(int... x, int t) { } // bad var-arg syntax } Variable Declarations There are two types of variables in Java: n Primitives A primitive can be one of eight types: char, boolean, byte, short, int, long, double, or float. Once a primitive has been declared, its primitive type can never change, although in most cases its value can change. n Reference variables A reference variable is used to refer to (or access) an object. A reference variable is declared to be of a specific type and that type can never be changed. A reference variable can be used to refer to any object of the declared type, or of a subtype of the declared type (a compatible type). We'll talk a lot more about using a reference variable to refer to a subtype in Chapter 2, when we discuss polymorphism. Declaring Primitives and Primitive Ranges Primitive variables can be declared as class variables (statics), instance variables, method parameters, or local variables. You can declare one or more primitives, of the same primitive type, in a single line. In Chapter 3 we will discuss the various ways in which they can be initialized, but for now we'll leave you with a few examples of primitive variable declarations: byte b; boolean myBooleanPrimitive; int x, y, z; // declare three int primitives On previous versions of the exam you needed to know how to calculate ranges for all the Java primitives. For the current exam, you can skip some of that detail, but it's still important to understand that for the integer types the sequence from small to big is byte, short, int, long, and that doubles are bigger than floats. You will also need to know that the number types (both integer and floatingpoint types) are all signed, and how that affects their ranges. First, let's review the concepts. All six number types in Java are made up of a certain number of 8-bit bytes, and are signed, meaning they can be negative or positive. The leftmost bit (the most significant digit) is used to represent the sign, where a 1 means negative and 0 means positive, as shown in Figure 1-6. The rest of the bits represent the value, using two's complement notation. Variable Declarations (Exam Objectives 1.3 and 1.4) 49 50 Chapter 1: Declarations and Access Control FIGURE 1-6 The Sign bit for a byte Table 1-3 shows the primitive types with their sizes and ranges. Figure 1-6 shows that with a byte, for example, there are 256 possible numbers (or 28). Half of these are negative, and half -1 are positive. The positive range is one less than the negative range because the number zero is stored as a positive binary number. We use the formula -2(bits-1) to calculate the negative range, and we use 2(bits-1)–1 for the positive range. Again, if you know the first two columns of this table, you'll be in good shape for the exam. Type Bits Bytes Minimum Range Maximum Range byte 8 1 -27 27-1 short 16 2 -215 215-1 int 32 4 -231 231-1 long 64 8 -263 263-1 float 32 4 n/a n/a double 64 8 n/a n/a table 1-3 Ranges of Numeric Primitives The range for floating-point numbers is complicated to determine, but luckily you don't need to know these for the exam (although you are expected to know that a double holds 64 bits and a float 32). For boolean types there is not a range; a boolean can be only true or false. If someone asks you for the bit depth of a boolean, look them straight in the eye and say, "That's virtual-machine dependent." They'll be impressed. The char type (a character) contains a single, 16-bit Unicode character. Although the extended ASCII set known as ISO Latin-1 needs only 8 bits (256 different characters), a larger range is needed to represent characters found in languages other than English. Unicode characters are actually represented by unsigned 16-bit integers, which means 216 possible values, ranging from 0 to 65535 (216)-1. You'll learn in Chapter 3 that because a char is really an integer type, it can be assigned to any number type large enough to hold 65535 (which means anything larger than a short. Although both chars and shorts are 16-bit types, remember that a short uses 1 bit to represent the sign, so fewer positive numbers are acceptable in a short). Declaring Reference Variables Reference variables can be declared as static variables, instance variables, method parameters, or local variables. You can declare one or more reference variables, of the same type, in a single line. In Chapter 3 we will discuss the various ways in which they can be initialized, but for now we'll leave you with a few examples of reference variable declarations: Object o; Dog myNewDogReferenceVariable; String s1, s2, s3; // declare three String vars. Instance Variables Instance variables are defined inside the class, but outside of any method, and are only initialized when the class is instantiated. Instance variables are the fields that belong to each unique object. For example, the following code defines fields (instance variables) for the name, title, and manager for employee objects: class Employee { // define fields (instance variables) for employee instances private String name; private String title, Variable Declarations (Exam Objectives 1.3 and 1.4) 51 52 Chapter 1: Declarations and Access Control private String manager; // other code goes here including access methods for private // fields } The preceding Employee class says that each employee instance will know its own name, title, and manager. In other words, each instance can have its own unique values for those three fields. If you see the term "field," "instance variable," "property," or "attribute," they mean virtually the same thing. (There actually are subtle but occasionally important distinctions between the terms, but those distinctions aren't used on the exam.) For the exam, you need to know that instance variables n Can use any of the four access levels (which means they can be marked with any of the three access modifiers) n Can be marked final n Can be marked transient n Cannot be marked abstract n Cannot be marked synchronized n Cannot be marked strictfp n Cannot be marked native n Cannot be marked static, because then they'd become class variables. We've already covered the effects of applying access control to instance variables (it works the same way as it does for member methods). A little later in this chapter we'll look at what it means to apply the final or transient modifier to an instance variable. First, though, we'll take a quick look at the difference between instance and local variables. Figure 1-7 compares the way in which modifiers can be applied to methods vs. variables. FIGURE 1-7 Comparison of modifiers on variables vs. methods Local (Automatic/Stack/Method) Variables Local variables are variables declared within a method. That means the variable is not just initialized within the method, but also declared within the method. Just as the local variable starts its life inside the method, it's also destroyed when the method has completed. Local variables are always on the stack, not the heap. (We'll talk more about the stack and the heap in Chapter 3). Although the value of the variable might be passed into, say, another method that then stores the value in an instance variable, the variable itself lives only within the scope of the method. Just don't forget that while the local variable is on the stack, if the variable is an object reference, the object itself will still be created on the heap. There is no such thing as a stack object, only a stack variable. You'll often hear programmers use the phrase, "local object," but what they really mean is, "locally declared reference variable." So if you hear a programmer use that expression, you'll know that he's just too lazy to phrase it in a technically precise way. You can tell him we said that— unless he knows where we live. Local variable declarations can't use most of the modifiers that can be applied to instance variables, such as public (or the other access modifiers), transient, volatile, abstract, or static, but as we saw earlier, local variables can be marked final. And as you'll learn in Chapter 3 (but here's a preview), before a local variable can be used, it must be initialized with a value. For instance: Variable Declarations (Exam Objectives 1.3 and 1.4) 53 54 Chapter 1: Declarations and Access Control class TestServer { public void logIn() { int count = 10; } } Typically, you'll initialize a local variable in the same line in which you declare it, although you might still need to reinitialize it later in the method. The key is to remember that a local variable must be initialized before you try to use it. The compiler will reject any code that tries to use a local variable that hasn't been assigned a value, because—unlike instance variables—local variables don't get default values. A local variable can't be referenced in any code outside the method in which it's declared. In the preceding code example, it would be impossible to refer to the variable count anywhere else in the class except within the scope of the method logIn(). Again, that's not to say that the value of count can't be passed out of the method to take on a new life. But the variable holding that value, count, can't be accessed once the method is complete, as the following illegal code demonstrates: class TestServer { public void logIn() { int count = 10; } public void doSomething(int i) { count = i; // Won't compile! Can't access count outside // method login() } } It is possible to declare a local variable with the same name as an instance variable. It's known as shadowing, as the following code demonstrates: class TestServer { int count = 9; // Declare an instance variable named count public void logIn() { int count = 10; // Declare a local variable named count System.out.println("local variable count is " + count); } public void count() { System.out.println("instance variable count is " + count); } public static void main(String[] args) { new TestServer().logIn(); new TestServer().count(); } } The preceding code produces the following output: local variable count is 10 instance variable count is 9 Why on earth (or the planet of your choice) would you want to do that? Normally, you won't. But one of the more common reasons is to name a parameter with the same name as the instance variable to which the parameter will be assigned. The following (wrong) code is trying to set an instance variable's value using a parameter: class Foo { int size = 27; public void setSize(int size) { size = size; // ??? which size equals which size??? } } So you've decided that—for overall readability—you want to give the parameter the same name as the instance variable its value is destined for, but how do you resolve the naming collision? Use the keyword this. The keyword this always, always, always refers to the object currently running. The following code shows this in action: class Foo { int size = 27; public void setSize(int size) { this.size = size; // this.size means the current object's // instance variable, size. The size // on the right is the parameter } } Array Declarations In Java, arrays are objects that store multiple variables of the same type, or variables that are all subclasses of the same type. Arrays can hold either primitives or object Variable Declarations (Exam Objectives 1.3 and 1.4) 55 56 Chapter 1: Declarations and Access Control references, but the array itself will always be an object on the heap, even if the array is declared to hold primitive elements. In other words, there is no such thing as a primitive array, but you can make an array of primitives. For the exam, you need to know three things: n How to make an array reference variable (declare) n How to make an array object (construct) n How to populate the array with elements (initialize) For this objective, you only need to know how to declare an array, we'll cover constructing and initializing arrays in Chapter 3. Arrays are efficient, but many times you'll want to use one of the Collection types from java.util (including HashMap, ArrayList, and TreeSet). Collection classes offer more flexible ways to access an object (for insertion, deletion, reading, and so on) and unlike arrays, can expand or contract dynamically as you add or remove elements. There's a Collection type for a wide range of needs. Do you need a fast sort? A group of objects with no duplicates? A way to access a name-value pair? Chapter 7 covers them in more detail. Arrays are declared by stating the type of elements the array will hold (an object or a primitive), followed by square brackets to either side of the identifier. Declaring an Array of Primitives int[] key; // Square brackets before name (recommended) int key []; // Square brackets after name (legal but less // readable) Declaring an Array of Object References Thread[] threads; // Recommended Thread threads []; // Legal but less readable When declaring an array reference, you should always put the array brackets immediately after the declared type, rather than after the identifier (variable name). That way, anyone reading the code can easily tell that, for example, key is a reference to an int array object, and not an int primitive. We can also declare multidimensional arrays, which are in fact arrays of arrays. This can be done in the following manner: String[][][] occupantName; String[] ManagerName []; The first example is a three-dimensional array (an array of arrays of arrays) and the second is a two-dimensional array. Notice in the second example we have one square bracket before the variable name and one after. This is perfectly legal to the compiler, proving once again that just because it's legal doesn't mean it's right. In Chapter 3, we'll spend a lot of time discussing arrays, how to initialize and use them, and how to deal with multi-dimensional arrays…stay tuned! Final Variables Declaring a variable with the final keyword makes it impossible to reinitialize that variable once it has been initialized with an explicit value (notice we said explicit rather than default). For primitives, this means that once the variable is assigned a value, the value can't be altered. For example, if you assign 10 to the int variable x, then x is going to stay 10, forever. So that's straightforward for primitives, but what does it mean to have a final object reference variable? A reference variable marked final can't ever be reassigned to refer to a different object. The data within the object can be modified, but the reference variable cannot be changed. In other words, a final reference still allows you to modify the state of the object it refers Variable Declarations (Exam Objectives 1.3 and 1.4) 57 It is never legal to include the size of the array in your declaration. Yes, we know you can do that in some other languages, which is why you might see a question or two that include code similar to the following: int[5] scores; The preceding code won’t compile. Remember, the JVM doesn’t allocate space until you actually instantiate the array object. That’s when size matters. 58 Chapter 1: Declarations and Access Control to, but you can't modify the reference variable to make it refer to a different object. Burn this in: there are no final objects, only final references. We'll explain this in more detail in Chapter 3. We've now covered how the final modifier can be applied to classes, methods, and variables. Figure 1-8 highlights the key points and differences of the various applications of final. FIGURE 1- 8 Effect of final on variables, methods, and classes Transient Variables If you mark an instance variable as transient, you're telling the JVM to skip (ignore) this variable when you attempt to serialize the object containing it. Serialization is one of the coolest features of Java; it lets you save (sometimes called "flatten") an object by writing its state (in other words, the value of its instance variables) to a special type of I/O stream. With serialization you can save an object to a file, or even ship it over a wire for reinflating (deserializing) at the other end, in another JVM. Serialization has been added to the exam as of Java 5, and we'll cover it in great detail in Chapter 6. Volatile Variables The volatile modifier tells the JVM that a thread accessing the variable must always reconcile its own private copy of the variable with the master copy in memory. Say what? Don't worry about it. For the exam, all you need to know about volatile is that, as with transient, it can be applied only to instance variables. Make no mistake, the idea of multiple threads accessing an instance variable is scary stuff, and very important for any Java programmer to understand. But as you'll see in Chapter 11, you'll probably use synchronization, rather than the volatile modifier, to make your data thread-safe. The volatile modifier may also be applied to project managers : ) Static Variables and Methods The static modifier is used to create variables and methods that will exist independently of any instances created for the class. In other words, static members exist before you ever make a new instance of a class, and there will be only one copy of the static member regardless of the number of instances of that class. In other words, all instances of a given class share the same value for any given static variable. We'll cover static members in great detail in the next chapter. Things you can mark as static: n Methods n Variables n A class nested within another class, but not within a method (more on this in Chapter 8). n Initialization blocks Variable Declarations (Exam Objectives 1.3 and 1.4) 59 60 Chapter 1: Declarations and Access Control Things you can't mark as static: n Constructors (makes no sense; a constructor is used only to create instances) n Classes (unless they are nested) n Interfaces n Method local inner classes (we'll explore this in Chapter 8) n Inner class methods and instance variables n Local variables Declaring Enums As of 5.0, Java lets you restrict a variable to having one of only a few pre-defined values—in other words, one value from an enumerated list. (The items in the enumerated list are called, surprisingly, enums.) Using enums can help reduce the bugs in your code. For instance, in your coffee shop application you might want to restrict your size selections to BIG, HUGE, and OVERWHELMING. If you let an order for a LARGE or a GRANDE slip in, it might cause an error. Enums to the rescue. With the following simple declaration, you can guarantee that the compiler will stop you from assigning anything to a CoffeeSize except BIG, HUGE, or OVERWHELMING: enum CoffeeSize { BIG, HUGE, OVERWHELMING }; From then on, the only way to get a CoffeeSize will be with a statement something like this: CoffeeSize cs = CoffeeSize.BIG; It's not required that enum constants be in all caps, but borrowing from the Sun code convention that constants are named in caps, it's a good idea. The basic components of an enum are its constants (i.e., BIG, HUGE, and OVERWHELMING), although in a minute you'll see that there can be a lot more to an enum. Enums can be declared as their own separate class, or as a class member, however they must not be declared within a method! Declaring an enum outside a class: enum CoffeeSize { BIG, HUGE, OVERWHELMING } // this cannot be // private or protected class Coffee { CoffeeSize size; } public class CoffeeTest1 { public static void main(String[] args) { Coffee drink = new Coffee(); drink.size = CoffeeSize.BIG; // enum outside class } } The preceding code can be part of a single file. (Remember, the file must be named CoffeeTest1.java because that's the name of the public class in the file.) The key point to remember is that the enum can be declared with only the public or default modifier, just like a non-inner class. Here's an example of declaring an enum inside a class: class Coffee2 { enum CoffeeSize {BIG, HUGE, OVERWHELMING } CoffeeSize size; } public class CoffeeTest2 { public static void main(String[] args) { Coffee2 drink = new Coffee2(); drink.size = Coffee2.CoffeeSize.BIG; // enclosing class // name required } } The key points to take away from these examples are that enums can be declared as their own class, or enclosed in another class, and that the syntax for accessing an enum's members depends on where the enum was declared. Declaring Enums (Exam Objectives 1.3 and 1.4) 61 62 Chapter 1: Declarations and Access Control The following is NOT legal: public class CoffeeTest1 { public static void main(String[] args) { enum CoffeeSize { BIG, HUGE, OVERWHELMING } // WRONG! Cannot // declare enums // in methods Coffee drink = new Coffee(); drink.size = CoffeeSize.BIG; } } To make it more confusing for you, the Java language designers made it optional to put a semicolon at the end of the enum declaration: public class CoffeeTest1 { enum CoffeeSize { BIG, HUGE, OVERWHELMING }; // <--semicolon // is optional here public static void main(String[] args) { Coffee drink = new Coffee(); drink.size = CoffeeSize.BIG; } } So what gets created when you make an enum? The most important thing to remember is that enums are not Strings or ints! Each of the enumerated CoffeeSize types are actually instances of CoffeeSize. In other words, BIG is of type CoffeeSize. Think of an enum as a kind of class, that looks something (but not exactly) like this: // conceptual example of how you can think // about enums class CoffeeSize { public static final CoffeeSize BIG = new CoffeeSize("BIG", 0); public static final CoffeeSize HUGE = new CoffeeSize("HUGE", 1); public static final CoffeeSize OVERWHELMING = new CoffeeSize("OVERWHELMING", 2); public CoffeeSize(String enumName, int index) { // stuff here } public static void main(String[] args) { System.out.println(CoffeeSize.BIG); } } Notice how each of the enumerated values, BIG, HUGE, and OVERWHELMING, are instances of type CoffeeSize. They're represented as static and final, which in the Java world, is thought of as a constant. Also notice that each enum value knows its index or position…in other words, the order in which enum values are declared matters. You can think of the CoffeeSize enums as existing in an array of type CoffeeSize, and as you'll see in a later chapter, you can iterate through the values of an enum by invoking the values() method on any enum type. (Don't worry about that in this chapter.) Declaring Constructors, Methods, and Variables in an enum Because an enum really is a special kind of class, you can do more than just list the enumerated constant values. You can add constructors, instance variables, methods, and something really strange known as a constant specific class body. To understand why you might need more in your enum, think about this scenario: imagine you want to know the actual size, in ounces, that map to each of the three CoffeeSize constants. For example, you want to know that BIG is 8 ounces, HUGE is 10 ounces, and OVERWHELMING is a whopping 16 ounces. You could make some kind of a lookup table, using some other data structure, but that would be a poor design and hard to maintain. The simplest way is to treat your enum values (BIG, HUGE, and OVERWHELMING), as objects that can each have their own instance variables. Then you can assign those values at the time the enums are initialized, by passing a value to the enum constructor. This takes a little explaining, but first look at the following code: enum CoffeeSize { BIG(8), HUGE(10), OVERWHELMING(16); // the arguments after the enum value are "passed" // as values to the constructor CoffeeSize(int ounces) { Declaring Enums (Exam Objectives 1.3 and 1.4) 63 64 Chapter 1: Declarations and Access Control this.ounces = ounces; // assign the value to // an instance variable } private int ounces; // an instance variable each enum // value has public int getOunces() { return ounces; } } class Coffee { CoffeeSize size; // each instance of Coffee has-a // CoffeeSize enum public static void main(String[] args) { Coffee drink1 = new Coffee(); drink1.size = CoffeeSize.BIG; Coffee drink2 = new Coffee(); drink2.size = CoffeeSize.OVERWHELMING; System.out.println(drink1.size.getOunces()); // prints 8 System.out.println(drink2.size.getOunces()); // prints 16 } } The key points to remember about enum constructors are n You can NEVER invoke an enum constructor directly. The enum constructor is invoked automatically, with the arguments you define after the constant value. For example, BIG(8) invokes the CoffeeSize constructor that takes an int, passing the int literal 8 to the constructor. (Behind the scenes, of course, you can imagine that BIG is also passed to the constructor, but we don't have to know—or care—about the details.) n You can define more than one argument to the constructor, and you can overload the enum constructors, just as you can overload a normal class constructor. We discuss constructors in much more detail in Chapter 2. To initialize a CoffeeType with both the number of ounces and, say, a lid type, you'd pass two arguments to the constructor as BIG(8, "A"), which means you have a constructor in CoffeeSize that takes both an int and a String. And finally, you can define something really strange in an enum that looks like an anonymous inner class (which we talk about in Chapter 8). It's known as a constant specific class body, and you use it when you need a particular constant to override a method defined in the enum. Imagine this scenario: you want enums to have two methods—one for ounces and one for lid code (a String). Now imagine that most coffee sizes use the same lid code, "B", but the OVERWHELMING size uses type "A". You can define a getLidCode() method in the CoffeeSize enum that returns "B", but then you need a way to override it for OVERWHELMING. You don't want to do some hard-tomaintain if/then code in the getLidCode() method, so the best approach might be to somehow have the OVERWHELMING constant override the getLidCode() method. This looks strange, but you need to understand the basic declaration rules: enum CoffeeSize { BIG(8), HUGE(10), OVERWHELMING(16) { // start a code block that defines // the "body" for this constant public String getLidCode() { // override the method // defined in CoffeeSize return "A"; } }; // <-- the semicolon is REQUIRED when you have a body CoffeeSize(int ounces) { this.ounces = ounces; } private int ounces; public int getOunces() { return ounces; } public String getLidCode() { // this method is overridden // by the OVERWHELMING constant return "B"; // the default value we want to return for // CoffeeSize constants } } Declaring Enums (Exam Objectives 1.3 and 1.4) 65 66 Chapter 1: Declarations and Access Control Certification Sumary After absorbing the material in this chapter, you should be familiar with some of the nuances of the Java language. You may also be experiencing confusion around why you ever wanted to take this exam in the first place. That's normal at this point. If you hear yourself saying, "What was I thinking?" just lie down until it passes. We would like to tell you that it gets easier…that this was the toughest chapter and it's all downhill from here… Let's briefly review what you'll need to know for the exam. There will be many questions dealing with keywords indirectly, so be sure you can identify which are keywords and which aren't. Although naming conventions like the use of camelCase won't be on the exam directly, you will need to understand the basics of JavaBeans naming, which uses camelCase. You need to understand the rules associated with creating legal identifiers, and the rules associated with source code declarations, including the use of package and import statements. You now have a good understanding of access control as it relates to classes, methods, and variables. You've looked at how access modifiers (public, protected, and private) define the access control of a class or member. You learned that abstract classes can contain both abstract and nonabstract methods, but that if even a single method is marked abstract, the class must be marked abstract. Don't forget that a concrete (nonabstract) subclass of an abstract class must provide implementations for all the abstract methods of the superclass, but that an abstract class does not have to implement the abstract methods from its superclass. An abstract subclass can "pass the buck" to the first concrete subclass. We covered interface implementation. Remember that interfaces can extend another interface (even multiple interfaces), and that any class that implements an interface must implement all methods from all the interfaces in the inheritance tree of the interface the class is implementing. You've also looked at the other modifiers including static, final, abstract, synchronized, and so on. You've learned how some modifiers can never be combined in a declaration, such as mixing abstract with either final or private. Keep in mind that there are no final objects in Java. A reference variable marked final can never be changed, but the object it refers to can be modified. You've seen that final applied to methods means a subclass can't override them, and when applied to a class, the final class can't be subclassed. Remember that as of Java 5, methods can be declared with a var-arg parameter (which can take from zero to many arguments of the declared type), but that you can have only one var-arg per method, and it must be the method's last parameter. Make sure you're familiar with the relative sizes of the numeric primitives. Remember that while the values of non-final variables can change, a reference variable's type can never change. You also learned that arrays are objects that contain many variables of the same type. Arrays can also contain other arrays. Remember what you've learned about static variables and methods, especially that static members are per-class as opposed to per-instance. Don't forget that a static method can't directly access an instance variable from the class it's in, because it doesn't have an explicit reference to any particular instance of the class. Finally, we covered a feature new to Java 5, enums. An enum is a much safer and more flexible way to implement constants than was possible in earlier versions of Java. Because they are a special kind of class, enums can be declared very simply, or they can be quite complex—including such attributes as methods, variables, constructors, and a special type of inner class called a constant specific class body. Before you hurl yourself at the practice test, spend some time with the following optimistically named "Two-Minute Drill." Come back to this particular drill often, as you work through this book and especially when you're doing that last-minute cramming. Because—and here's the advice you wished your mother had given you before you left for college—it's not what you know, it's when you know it. For the exam, knowing what you can't do with the Java language is just as important as knowing what you can do. Give the sample questions a try! They're very similar to the difficulty and structure of the real exam questions, and should be an eye opener for how difficult the exam can be. Don't worry if you get a lot of them wrong. If you find a topic that you are weak in, spend more time reviewing and studying. Many programmers need two or three serious passes through a chapter (or an individual objective) before they can answer the questions confidently. Certification Summary 67 68 Chapter 1: Declarations and Access Control 3 Two-Minute Dril Remember that in this chapter, when we talk about classes, we're referring to non-inner classes, or top-level classes. We'll devote all of Chapter 8 to inner classes. Identifiers (Objective 1.3) q Identifiers can begin with a letter, an underscore, or a currency character. q After the first character, identifiers can also include digits. q Identifiers can be of any length. q JavaBeans methods must be named using camelCase, and depending on the method's purpose, must start with set, get, is, add, or remove. Declaration Rules (Objective 1.1) q A source code file can have only one public class. q If the source file contains a public class, the filename must match the public class name. q A file can have only one package statement, but multiple imports. q The package statement (if any) must be the first (non-comment) line in a source file. q The import statements (if any) must come after the package and before the class declaration. q If there is no package statement, import statements must be the first (noncomment) statements in the source file. q package and import statements apply to all classes in the file. q A file can have more than one nonpublic class. q Files with no public classes have no naming restrictions. Class Access Modifiers (Objective 1.1) q There are three access modifiers: public, protected, and private. q There are four access levels: public, protected, default, and private. q Classes can have only public or default access. q A class with default access can be seen only by classes within the same package. q A class with public access can be seen by all classes from all packages. q Class visibility revolves around whether code in one class can q Create an instance of another class q Extend (or subclass), another class q Access methods and variables of another class Class Modifiers (Nonaccess) (Objective 1.2) q Classes can also be modified with final, abstract, or strictfp. q A class cannot be both final and abstract. q A final class cannot be subclassed. q An abstract class cannot be instantiated. q A single abstract method in a class means the whole class must be abstract. q An abstract class can have both abstract and nonabstract methods. q The first concrete class to extend an abstract class must implement all of its abstract methods. Interface Implementation (Objective 1.2) q Interfaces are contracts for what a class can do, but they say nothing about the way in which the class must do it. q Interfaces can be implemented by any class, from any inheritance tree. q An interface is like a 100-percent abstract class, and is implicitly abstract whether you type the abstract modifier in the declaration or not. q An interface can have only abstract methods, no concrete methods allowed. q Interface methods are by default public and abstract—explicit declaration of these modifiers is optional. q Interfaces can have constants, which are always implicitly public, static, and final. q Interface constant declarations of public, static, and final are optional in any combination. q A legal nonabstract implementing class has the following properties: q It provides concrete implementations for the interface's methods. q It must follow all legal override rules for the methods it implements. q It must not declare any new checked exceptions for an implementation method. Two-Minute Drill 69 70 Chapter 1: Declarations and Access Control q It must not declare any checked exceptions that are broader than the exceptions declared in the interface method. q It may declare runtime exceptions on any interface method implementation regardless of the interface declaration. q It must maintain the exact signature (allowing for covariant returns) and return type of the methods it implements (but does not have to declare the exceptions of the interface). q A class implementing an interface can itself be abstract. q An abstract implementing class does not have to implement the interface methods (but the first concrete subclass must). q A class can extend only one class (no multiple inheritance), but it can implement many interfaces. q Interfaces can extend one or more other interfaces. q Interfaces cannot extend a class, or implement a class or interface. q When taking the exam, verify that interface and class declarations are legal before verifying other code logic. Member Access Modifiers (Objectives 1.3 and 1.4) q Methods and instance (nonlocal) variables are known as "members." q Members can use all four access levels: public, protected, default, private. q Member access comes in two forms: q Code in one class can access a member of another class. q A subclass can inherit a member of its superclass. q If a class cannot be accessed, its members cannot be accessed. q Determine class visibility before determining member visibility. q public members can be accessed by all other classes, even in other packages. q If a superclass member is public, the subclass inherits it—regardless of package. q Members accessed without the dot operator (.) must belong to the same class. q this. always refers to the currently executing object. q this.aMethod() is the same as just invoking aMethod(). q private members can be accessed only by code in the same class. q private members are not visible to subclasses, so private members cannot be inherited. q Default and protected members differ only when subclasses are involved: q Default members can be accessed only by classes in the same package. q protected members can be accessed by other classes in the same package, plus subclasses regardless of package. q protected = package plus kids (kids meaning subclasses). q For subclasses outside the package, the protected member can be accessed only through inheritance; a subclass outside the package cannot access a protected member by using a reference to a superclass instance (in other words, inheritance is the only mechanism for a subclass outside the package to access a protected member of its superclass). q A protected member inherited by a subclass from another package is not accessible to any other class in the subclass package, except for the subclass' own subclasses. Local Variables (Objective 1.3) q Local (method, automatic, or stack) variable declarations cannot have access modifiers. q final is the only modifier available to local variables. q Local variables don't get default values, so they must be initialized before use. Other Modifiers—Members (Objective 1.3) q final methods cannot be overridden in a subclass. q abstract methods are declared, with a signature, a return type, and an optional throws clause, but are not implemented. q abstract methods end in a semicolon—no curly braces. q Three ways to spot a non-abstract method: q The method is not marked abstract. q The method has curly braces. q The method has code between the curly braces. q The first nonabstract (concrete) class to extend an abstract class must implement all of the abstract class' abstract methods. q The synchronized modifier applies only to methods and code blocks. q synchronized methods can have any access control and can also be marked final. Two-Minute Drill 71 72 Chapter 1: Declarations and Access Control q abstract methods must be implemented by a subclass, so they must be inheritable. For that reason: q abstract methods cannot be private. q abstract methods cannot be final. q The native modifier applies only to methods. q The strictfp modifier applies only to classes and methods. Methods with var-args (Objective 1.4) q As of Java 5, methods can declare a parameter that accepts from zero to many arguments, a so-called var-arg method. q A var-arg parameter is declared with the syntax type... name; for instance: doStuff(int... x) { } q A var-arg method can have only one var-arg parameter. q In methods with normal parameters and a var-arg, the var-arg must come last. Variable Declarations (Objective 1.3) q Instance variables can q Have any access control q Be marked final or transient q Instance variables can't be abstract, synchronized, native, or strictfp. q It is legal to declare a local variable with the same name as an instance variable; this is called "shadowing." q final variables have the following properties: q final variables cannot be reinitialized once assigned a value. q final reference variables cannot refer to a different object once the object has been assigned to the final variable. q final reference variables must be initialized before the constructor completes. q There is no such thing as a final object. An object reference marked final does not mean the object itself is immutable. q The transient modifier applies only to instance variables. q The volatile modifier applies only to instance variables. Array Declarations (Objective 1.3) q Arrays can hold primitives or objects, but the array itself is always an object. q When you declare an array, the brackets can be to the left or right of the variable name. q It is never legal to include the size of an array in the declaration. q An array of objects can hold any object that passes the IS-A (or instanceof) test for the declared type of the array. For example, if Horse extends Animal, then a Horse object can go into an Animal array. Static Variables and Methods (Objective 1.4) q They are not tied to any particular instance of a class. q No classes instances are needed in order to use static members of the class. q There is only one copy of a static variable / class and all instances share it. q static methods do not have direct access to non-static members. Enums (Objective 1.3) q An enum specifies a list of constant values that can be assigned to a particular type. q An enum is NOT a String or an int; an enum constant's type is the enum type. For example, WINTER, SPRING, SUMMER, and FALL are of the enum type Season. q An enum can be declared outside or inside a class, but NOT in a method. q An enum declared outside a class must NOT be marked static, final, abstract, protected, or private. q Enums can contain constructors, methods, variables, and constant class bodies. q enum constants can send arguments to the enum constructor, using the syntax BIG(8), where the int literal 8 is passed to the enum constructor. q enum constructors can have arguments, and can be overloaded. q enum constructors can NEVER be invoked directly in code. They are always called automatically when an enum is initialized. q The semicolon at the end of an enum declaration is optional. These are legal: enum Foo { ONE, TWO, THREE} enum Foo { ONE, TWO, THREE}; Two-Minute Drill 73 Self Test The following questions will help you measure your understanding of the material presented in this chapter. Read all of the choices carefully, as there may be more than one correct answer. Choose all correct answers for each question. Stay focused. If you have a rough time with these at first, don't beat yourself up. Be positive. Repeat nice affirmations to yourself like, "I am smart enough to understand enums" and "OK, so that other guy knows enums better than I do, but I bet he can't <insert something you are good at> like me." 1. Given the following, 1. interface Base { 2. boolean m1 (); 3. byte m2(short s); 4. } Which code fragments will compile? (Choose all that apply.) A. interface Base2 implements Base { } B. abstract class Class2 extends Base { public boolean m1() { return true; } } C. abstract class Class2 implements Base { } D. abstract class Class2 implements Base { public boolean m1() { return (true); } } E. class Class2 implements Base { boolean m1() { return false; } byte m2(short s) { return 42; } } 2. Which declare a compilable abstract class? (Choose all that apply.) A. public abstract class Canine { public Bark speak(); } B. public abstract class Canine { public Bark speak() { } } C. public class Canine { public abstract Bark speak(); } D. public class Canine abstract { public abstract Bark speak(); } 3. Which is true? (Choose all that apply.) A. "X extends Y" is correct if and only if X is a class and Y is an interface. B. "X extends Y" is correct if and only if X is an interface and Y is a class. C. "X extends Y" is correct if X and Y are either both classes or both interfaces. D. "X extends Y" is correct for all combinations of X and Y being classes and/or interfaces. 74 Chapter 1: Declarations and Access Control 4. Which are valid declarations? (Choose all that apply.) A. int $x; B. int 123; C. int _123; D. int #dim; E. int %percent; F. int *divide; G. int central_sales_region_Summer_2005_gross_sales; 5. Which method names follow the JavaBeans standard? (Choose all that apply.) A. addSize B. getCust C. deleteRep D. isColorado E. putDimensions 6. Given: 1. class Voop { 2. public static void main(String [] args) { 3. doStuff(1); 4. doStuff(1,2); 5. } 6. // insert code here 7. } Which, inserted independently at line 6, will compile? (Choose all that apply.) A. static void doStuff(int... doArgs) { } B. static void doStuff(int[] doArgs) { } C. static void doStuff(int doArgs...) { } D. static void doStuff(int... doArgs, int y) { } E. static void doStuff(int x, int... doArgs) { } 7. Which are legal declarations? (Choose all that apply.) A. short x []; B. short [] y; C. short[5] x2; D. short z2 [5]; E. short [] z [] []; F. short [] y2 = [5]; Self Test 75 76 Chapter 1: Declarations and Access Control 8. Given: 1. enum Animals { 2. DOG("woof"), CAT("meow"), FISH("burble"); 3. String sound; 4. Animals(String s) { sound = s; } 5. } 6. class TestEnum { 7. static Animals a; 8. public static void main(String[] args) { 9. System.out.println(a.DOG.sound + " " + a.FISH.sound); 10. } 11. } What is the result? A. woof burble B. Multiple compilation errors C. Compilation fails due to an error on line 2 D. Compilation fails due to an error on line 3 E. Compilation fails due to an error on line 4 F. Compilation fails due to an error on line 9 9. Given: 1. enum A { A } 2. class E2 { 3. enum B { B } 4. void C() { 5. enum D { D } 6. } 7. } Which statements are true? (Choose all that apply.) A. The code compiles. B. If only line 1 is removed the code compiles. C. If only line 3 is removed the code compiles. D. If only line 5 is removed the code compiles. E. If lines 1 and 3 are removed the code compiles F. If lines 1, 3 and 5 are removed the code compiles. SElf Test Answers 1. Given the following, 1. interface Base { 2. boolean m1 (); 3. byte m2(short s); 4. } Which code fragments will compile? (Choose all that apply.) A. interface Base2 implements Base { } B. abstract class Class2 extends Base { public boolean m1() { return true; } } C. abstract class Class2 implements Base { } D. abstract class Class2 implements Base { public boolean m1() { return (true); } } E. class Class2 implements Base { boolean m1() { return false; } byte m2(short s) { return 42; } } Answer: ® 3 C and D are correct. C is correct because an abstract class doesn't have to implement any or all of its interface's methods. D is correct because the method is correctly implemented. ®˚ A is incorrect because interfaces don't implement anything. B is incorrect because classes don't extend interfaces. E is incorrect because interface methods are implicitly public, so the methods being implemented must be public. (Objective 1.1) 2. Which declare a compilable abstract class? (Choose all that apply.) A. public abstract class Canine { public Bark speak(); } B. public abstract class Canine { public Bark speak() { } } C. public class Canine { public abstract Bark speak(); } D. public class Canine abstract { public abstract Bark speak(); } Answer: ® 3 B is correct. abstract classes don't have to have any abstract methods. ®˚ A is incorrect because abstract methods must be marked as such. C is incorrect because you can't have an abstract method unless the class is abstract. D is incorrect because the keyword abstract must come before the classname. (Objective 1.1) Self Test Answers 77 78 Chapter 1: Declarations and Access Control 3. Which is true? (Choose all that apply.) A. "X extends Y" is correct if and only if X is a class and Y is an interface. B. "X extends Y" is correct if and only if X is an interface and Y is a class. C. "X extends Y" is correct if X and Y are either both classes or both interfaces. D. "X extends Y" is correct for all combinations of X and Y being classes and/or interfaces. Answer: ® 3 C is correct. ®˚ A is incorrect because classes implement interfaces, they don't extend them. B is incorrect because interfaces only "inherit from" other interfaces. D is incorrect based on the preceding rules. (Objective 1.2) 4. Which are valid declarations? (Choose all that apply.) A. int $x; B. int 123; C. int _123; D. int #dim; E. int %percent; F. int *divide; G. int central_sales_region_Summer_2005_gross_sales; Answer: ® 3 A, C, and G are legal identifiers. ®˚ B is incorrect because an identifier can't start with a digit. D, E, and F are incorrect because identifiers must start with $, _, or a letter. (Objective 1.3) 5. Which method names follow the JavaBeans standard? (Choose all that apply.) A. addSize B. getCust C. deleteRep D. isColorado E. putDimensions Answer: ® 3 B and D use the valid prefixes 'get' and 'is'. ®˚ A, C, and E are incorrect because 'add', 'delete' and 'put' are not standard JavaBeans name prefixes. (Objective 1.4) 6. Given: 1. class Voop { 2. public static void main(String[] args) { 3. doStuff(1); 4. doStuff(1,2); 5. } 6. // insert code here 7. } Which, inserted independently at line 6, will compile? (Choose all that apply.) A. static void doStuff(int... doArgs) { } B. static void doStuff(int[] doArgs) { } C. static void doStuff(int doArgs...) { } D. static void doStuff(int... doArgs, int y) { } E. static void doStuff(int x, int... doArgs) { } Answer: ® 3 A and E use valid var-args syntax. ®˚ B and C are invalid var-arg syntax, and D is invalid because the var-arg must be the last of a method's arguments. (Objective 1.4) 7. Which are legal declarations? (Choose all that apply.) A. short x []; B. short [] y; C. short[5] x2; D. short z2 [5]; E. short [] z [] []; F. short [] y2 = [5]; Answer: ® 3 A, B, and E are correct array declarations; E is a three dimensional array. ®˚ C, D, and F are incorrect, you can't include the size of your array in a declaration unless you also instantiate the array object. F uses invalid instantiation syntax. (Objective 1.3) 8. Given: 1. enum Animals { 2. DOG("woof"), CAT("meow"), FISH("burble"); 3. String sound; 4. Animals(String s) { sound = s; } 5. } 6. class TestEnum { Self Test Answers 79 80 Chapter 1: Declarations and Access Control 7. static Animals a; 8. public static void main(String [] args) { 9. System.out.println(a.DOG.sound + " " + a.FISH.sound); 10. } 11. } What is the result? A. woof burble B. Multiple compilation errors C. Compilation fails due to an error on line 2 D. Compilation fails due to an error on line 3 E. Compilation fails due to an error on line 4 F. Compilation fails due to an error on line 9 Answer: ® 3 A is correct; enums can have constructors and variables. ®˚ B, C, D, E, and F are incorrect; these lines all use correct syntax. 9. Given: 1. enum A { A } 2. class E2 { 3. enum B { B } 4. void C() { 5. enum D { D } 6. } 7. } Which statements are true? (Choose all that apply.) A. The code compiles. B. If only line 1 is removed the code compiles. C. If only line 3 is removed the code compiles. D. If only line 5 is removed the code compiles. E. If lines 1 and 3 are removed the code compiles. F. If lines 1, 3 and 5 are removed the code compiles. Answer: ® 3 D and F are correct. Line 5 is the only line that will not compile, because enums cannot be local to a method. ®˚ A, B, C and E are incorrect based on the above.









2 Object Orientation CERTIFICATION OBJECTIVES l Declare Interfaces l Declare, Initialize, and Use Class Members l Use Overloading and Overriding l Develop Constructors l Describe Encapsulation, Coupling, and Cohesion l Use Polymorphism l Relate Modifiers and Inheritance l Use Superclass Constructors and Overloaded Constructors l Use IS-A and HAS-A Relationships 3 Two-Minute Drill Q&A Self Test 82 Chapter 2: Object Orientation Being a 1.5 SCJP means you must be at one with the object-oriented aspects of Java. You must dream of inheritance hierarchies, the power of polymorphism must flow through you, cohesion and loose coupling must become second nature to you, and composition must be your bread and butter. This chapter will prepare you for all of the object-oriented objectives and questions you'll encounter on the exam. We have heard of many experienced Java programmers who haven't really become fluent with the object-oriented tools that Java provides, so we'll start at the beginning. CERTIFICATION OBJECTIVE Encapsulation (Exam Objective 5.1) 5.1 Develop code that implements tight encapsulation, loose coupling, and high cohesion in classes, and describe the benefits. Imagine you wrote the code for a class, and another dozen programmers from your company all wrote programs that used your class. Now imagine that later on, you didn't like the way the class behaved, because some of its instance variables were being set (by the other programmers from within their code) to values you hadn't anticipated. Their code brought out errors in your code. (Relax, this is just hypothetical.) Well, it is a Java program, so you should be able just to ship out a newer version of the class, which they could replace in their programs without changing any of their own code. This scenario highlights two of the promises/benefits of Object Orientation: flexibility and maintainability. But those benefits don't come automatically. You have to do something. You have to write your classes and code in a way that supports flexibility and maintainability. So what if Java supports OO? It can't design your code for you. For example, imagine if you made your class with public instance variables, and those other programmers were setting the instance variables directly, as the following code demonstrates: public class BadOO { public int size; Encapsulation (Exam Objective 5.1) 83 public int weight; ... } public class ExploitBadOO { public static void main (String [] args) { BadOO b = new BadOO(); b.size = -5; // Legal but bad!! } } And now you're in trouble. How are you going to change the class in a way that lets you handle the issues that come up when somebody changes the size variable to a value that causes problems? Your only choice is to go back in and write method code for adjusting size (a setSize(int a) method, for example), and then protect the size variable with, say, a private access modifier. But as soon as you make that change to your code, you break everyone else's! The ability to make changes in your implementation code without breaking the code of others who use your code is a key benefit of encapsulation. You want to hide implementation details behind a public programming interface. By interface, we mean the set of accessible methods your code makes available for other code to call—in other words, your code's API. By hiding implementation details, you can rework your method code (perhaps also altering the way variables are used by your class) without forcing a change in the code that calls your changed method. If you want maintainability, flexibility, and extensibility (and of course, you do), your design must include encapsulation. How do you do that? n Keep instance variables protected (with an access modifier, often private). n Make public accessor methods, and force calling code to use those methods rather than directly accessing the instance variable. n For the methods, use the JavaBeans naming convention of set<someProperty> and get<someProperty>. Figure 2-1 illustrates the idea that encapsulation forces callers of our code to go through methods rather than accessing variables directly. FIGURE 2-1 The nature of encapsulation We call the access methods getters and setters although some prefer the fancier terms accessors and mutators. (Personally, we don't like the word "mutate".) Regardless of what you call them, they're methods that other programmers must go through in order to access your instance variables. They look simple, and you've probably been using them forever: public class Box { // protect the instance variable; only an instance // of Box can access it " d " "dfdf" private int size; // Provide public getters and setters public int getSize() { return size; 84 Chapter 2: Object Orientation } public void setSize(int newSize) { size = newSize; } } Wait a minute...how useful is the previous code? It doesn't even do any validation or processing. What benefit can there be from having getters and setters that add no additional functionality? The point is, you can change your mind later, and add more code to your methods without breaking your API. Even if today you don't think you really need validation or processing of the data, good OO design dictates that you plan for the future. To be safe, force calling code to go through your methods rather than going directly to instance variables. Always. Then you're free to rework your method implementations later, without risking the wrath of those dozen programmers who know where you live. Encapsulation (Exam Objective 5.1) 85 Look out for code that appears to be asking about the behavior of a method, when the problem is actually a lack of encapsulation. Look at the following example, and see if you can figure out what’s going on: class Foo { public int left = 9; public int right = 3; public void setLeft(int leftNum) { left = leftNum; right = leftNum/3; } // lots of complex test code here } Now consider this question: Is the value of right always going to be onethird the value of left? It looks like it will, until you realize that users of the Foo class don’t need to use the setLeft() method! They can simply go straight to the instance variables and change them to any arbitrary int value. CERTIFICATION objective Inheritance, Is-A, Has-A (Exam Objective 5.5) 5.5 Develop code that implements "is-a" and/or "has-a" relationships. Inheritance is everywhere in Java. It's safe to say that it's almost (almost?) impossible to write even the tiniest Java program without using inheritance. In order to explore this topic we're going to use the instanceof operator, which we'll discuss in more detail in Chapter 4. For now, just remember that instanceof returns true if the reference variable being tested is of the type being compared to. This code: class Test { public static void main(String [] args) { Test t1 = new Test(); Test t2 = new Test(); if (!t1.equals(t2)) System.out.println("they're not equal"); if (t1 instanceof Object) System.out.println("t1's an Object"); } } Produces the output: they're not equal t1's an Object Where did that equals method come from? The reference variable t1 is of type Test, and there's no equals method in the Test class. Or is there? The second if test asks whether t1 is an instance of class Object, and because it is (more on that soon), the if test succeeds. Hold on…how can t1 be an instance of type Object, we just said it was of type Test? I'm sure you're way ahead of us here, but it turns out that every class in Java is a subclass of class Object, (except of course class Object itself). In other words, every class you'll ever use or ever write will inherit from class Object. You'll always have an equals method, a clone method, notify, wait, and others, available to use. Whenever you create a class, you automatically inherit all of class Object's methods. 86 Chapter 2: Object Orientation Why? Let's look at that equals method for instance. Java's creators correctly assumed that it would be very common for Java programmers to want to compare instances of their classes to check for equality. If class Object didn't have an equals method, you'd have to write one yourself; you and every other Java programmer. That one equals method has been inherited billions of times. (To be fair, equals has also been overridden billions of times, but we're getting ahead of ourselves.) For the exam you'll need to know that you can create inheritance relationships in Java by extending a class. It's also important to understand that the two most common reasons to use inheritance are n To promote code reuse n To use polymorphism Let's start with reuse. A common design approach is to create a fairly generic version of a class with the intention of creating more specialized subclasses that inherit from it. For example: class GameShape { public void displayShape() { System.out.println("displaying shape"); } // more code } class PlayerPiece extends GameShape { public void movePiece() { System.out.println("moving game piece"); } // more code } public class TestShapes { public static void main (String[] args) { PlayerPiece shape = new PlayerPiece(); shape.displayShape(); shape.movePiece(); } } Inheritance, Is-A, Has-A (Exam Objective 5.5) 87 Outputs: displaying shape moving game piece Notice that the PlayingPiece class inherits the generic display() method from the less-specialized class GameShape, and also adds its own method, movePiece(). Code reuse through inheritance means that methods with generic functionality (like display())—that could apply to a wide range of different kinds of shapes in a game—don't have to be reimplemented. That means all specialized subclasses of GameShape are guaranteed to have the capabilities of the more generic superclass. You don't want to have to rewrite the display() code in each of your specialized components of an online game. But you knew that. You've experienced the pain of duplicate code when you make a change in one place and have to track down all the other places where that same (or very similar) code exists. The second (and related) use of inheritance is to allow your classes to be accessed polymorphically—a capability provided by interfaces as well, but we'll get to that in a minute. Let's say that you have a GameLauncher class that wants to loop through a list of different kinds of GameShape objects, and invoke display() on each of them. At the time you write this class, you don't know every possible kind of GameShape subclass that anyone else will ever write. And you sure don't want to have to redo your code just because somebody decided to build a Dice shape six months later. The beautiful thing about polymorphism ("many forms") is that you can treat any subclass of GameShape as a GameShape. In other words, you can write code in your GameLauncher class that says, "I don't care what kind of object you are as long as you inherit from (extend) GameShape. And as far as I'm concerned, if you extend GameShape then you've definitely got a display() method, so I know I can call it." Imagine we now have two specialized subclasses that extend the more generic GameShape class, PlayerPiece and TilePiece: class GameShape { public void displayShape() { System.out.println("displaying shape"); } // more code } 88 Chapter 2: Object Orientation class PlayerPiece extends GameShape { public void movePiece() { System.out.println("moving game piece"); } // more code } class TilePiece extends GameShape { public void getAdjacent() { System.out.println("getting adjacent tiles"); } // more code } Now imagine a test class has a method with a declared argument type of GameShape, that means it can take any kind of GameShape. In other words, any subclass of GameShape can be passed to a method with an argument of type GameShape. This code public class TestShapes { public static void main (String[] args) { PlayerPiece player = new PlayerPiece(); TilePiece tile = new TilePiece(); doShapes(player); doShapes(tile); } public static void doShapes(GameShape shape) { shape.displayShape(); } } Outputs: displaying shape displaying shape The key point is that the doShapes() method is declared with a GameShape argument but can be passed any subtype (in this example, a subclass) of GameShape. The method can then invoke any method of GameShape, without any concern for the actual runtime class type of the object passed to the method. There are Inheritance, Is-A, Has-A (Exam Objective 5.5) 89 implications, though. The doShapes() method knows only that the objects are a type of GameShape, since that's how the parameter is declared. And using a reference variable declared as type GameShape—regardless of whether the variable is a method parameter, local variable, or instance variable—means that only the methods of GameShape can be invoked on it. The methods you can call on a reference are totally dependent on the declared type of the variable, no matter what the actual object is, that the reference is referring to. That means you can't use a GameShape variable to call, say, the getAdjacent() method even if the object passed in is of type TilePiece. (We'll see this again when we look at interfaces.) IS-A and HAS-A Relationships For the exam you need to be able to look at code and determine whether the code demonstrates an IS-A or HAS-A relationship. The rules are simple, so this should be one of the few areas where answering the questions correctly is almost a no-brainer. IS-A In OO, the concept of IS-A is based on class inheritance or interface implementation. IS-A is a way of saying, "this thing is a type of that thing." For example, a Mustang is a type of horse, so in OO terms we can say, "Mustang IS-A Horse." Subaru IS-A Car. Broccoli IS-A Vegetable (not a very fun one, but it still counts). You express the IS-A relationship in Java through the keywords extends (for class inheritance) and implements (for interface implementation). public class Car { // Cool Car code goes here } public class Subaru extends Car { // Important Subaru-specific stuff goes here // Don't forget Subaru inherits accessible Car members which // can include both methods and variables. } A Car is a type of Vehicle, so the inheritance tree might start from the Vehicle class as follows: public class Vehicle { ... } public class Car extends Vehicle { ... } public class Subaru extends Car { ... } 90 Chapter 2: Object Orientation In OO terms, you can say the following: Vehicle is the superclass of Car. Car is the subclass of Vehicle. Car is the superclass of Subaru. Subaru is the subclass of Vehicle. Car inherits from Vehicle. Subaru inherits from both Vehicle and Car. Subaru is derived from Car. Car is derived from Vehicle. Subaru is derived from Vehicle. Subaru is a subtype of both Vehicle and Car. Returning to our IS-A relationship, the following statements are true: "Car extends Vehicle" means "Car IS-A Vehicle." "Subaru extends Car" means "Subaru IS-A Car." And we can also say: "Subaru IS-A Vehicle" because a class is said to be "a type of" anything further up in its inheritance tree. If the expression (Foo instanceof Bar) is true, then class Foo IS-A Bar, even if Foo doesn't directly extend Bar, but instead extends some other class that is a subclass of Bar. Figure 2-2 illustrates the inheritance tree for Vehicle, Car, and Subaru. The arrows move from the subclass to the superclass. In other words, a class' arrow points toward the class from which it extends. FIGURE 2-2 Inheritance tree for Vehicle, Car, Subaru Inheritance, Is-A, Has-A (Exam Objective 5.5) 91 HAS-A HAS-A relationships are based on usage, rather than inheritance. In other words, class A HAS-A B if code in class A has a reference to an instance of class B. For example, you can say the following, A Horse IS-A Animal. A Horse HAS-A Halter. The code might look like this: public class Animal { } public class Horse extends Animal { private Halter myHalter; } In the preceding code, the Horse class has an instance variable of type Halter, so you can say that "Horse HAS-A Halter." In other words, Horse has a reference to a Halter. Horse code can use that Halter reference to invoke methods on the Halter, and get Halter behavior without having Halter-related code (methods) in the Horse class itself. Figure 2-3 illustrates the HAS-A relationship between Horse and Halter. FIGURE 2-3 HAS-A relationship between Horse and Halter HAS-A relationships allow you to design classes that follow good OO practices by not having monolithic classes that do a gazillion different things. Classes (and their resulting objects) should be specialists. As our friend Andrew says, "specialized classes can actually help reduce bugs." The more specialized the class, the more likely it is that you can reuse the class in other applications. If you put all the Halter-related code directly into the Horse class, you'll end up duplicating code in the Cow class, UnpaidIntern class, and any other class that might need Halter behavior. By keeping the Halter code in a separate, specialized Halter class, you have the chance to reuse the Halter class in multiple applications. 92 Chapter 2: Object Orientation Inheritance, Is-A, Has-A (Exam Objective 5.5) 93 Object-Oriented Design IS-A and HAS-A relationships and encapsulation are just the tip of the iceberg when it comes to object-oriented design. Many books and graduate theses have been dedicated to this topic. The reason for the emphasis on proper design is simple: money. The cost to deliver a software application has been estimated to be as much as ten times more expensive for poorly designed programs. Having seen the ramifications of poor designs, I can assure you that this estimate is not far-fetched. Even the best object-oriented designers make mistakes. It is difficult to visualize the relationships between hundreds, or even thousands, of classes. When mistakes are discovered during the implementation (code writing) phase of a project, the amount of code that has to be rewritten can sometimes cause programming teams to start over from scratch. The software industry has evolved to aid the designer. Visual object modeling languages, like the Unified Modeling Language (UML), allow designers to design and easily modify classes without having to write code first, because object-oriented components are represented graphically. This allows the designer to create a map of the class relationships and helps them recognize errors before coding begins. Another innovation in object-oriented design is design patterns. Designers noticed that many object-oriented designs apply consistently from project to project, and that it was useful to apply the same designs because it reduced the potential to introduce new design errors. Object-oriented designers then started to share these designs with each other. Now, there are many catalogs of these design patterns both on the Internet and in book form. Although passing the Java certification exam does not require you to understand objectoriented design this thoroughly, hopefully this background information will help you better appreciate why the test writers chose to include encapsulation, and IS-A, and HAS-A relationships on the exam. - Jonathan Meeks, Sun Certified Java Programmer FROM THE CLASSROOM 94 Chapter 2: Object Orientation Users of the Horse class (that is, code that calls methods on a Horse instance), think that the Horse class has Halter behavior. The Horse class might have a tie(LeadRope rope) method, for example. Users of the Horse class should never have to know that when they invoke the tie() method, the Horse object turns around and delegates the call to its Halter class by invoking myHalter.tie(rope). The scenario just described might look like this: public class Horse extends Animal { private Halter myHalter; public void tie(LeadRope rope) { myHalter.tie(rope); // Delegate tie behavior to the // Halter object } } public class Halter { public void tie(LeadRope aRope) { // Do the actual tie work here } } In OO, we don't want callers to worry about which class or which object is actually doing the real work. To make that happen, the Horse class hides implementation details from Horse users. Horse users ask the Horse object to do things (in this case, tie itself up), and the Horse will either do it or, as in this example, ask something else to do it. To the caller, though, it always appears that the Horse object takes care of itself. Users of a Horse should not even need to know that there is such a thing as a Halter class. CERTIFICATION OBJECTIVE Polymorphism (Exam Objective 5.2) 5.2 Given a scenario, develop code that demonstrates the use of polymorphism. Further, determine when casting will be necessary and recognize compiler vs. runtime errors related to object reference casting. Polymorphism (Exam Objective 5.2) 95 Remember, any Java object that can pass more than one IS-A test can be considered polymorphic. Other than objects of type Object, all Java objects are polymorphic in that they pass the IS-A test for their own type and for class Object. Remember that the only way to access an object is through a reference variable, and there are a few key things to remember about references: n A reference variable can be of only one type, and once declared, that type can never be changed (although the object it references can change). n A reference is a variable, so it can be reassigned to other objects, (unless the reference is declared final). n A reference variable's type determines the methods that can be invoked on the object the variable is referencing. n A reference variable can refer to any object of the same type as the declared reference, or—this is the big one—it can refer to any subtype of the declared type! n A reference variable can be declared as a class type or an interface type. If the variable is declared as an interface type, it can reference any object of any class that implements the interface. Earlier we created a GameShape class that was extended by two other classes, PlayerPiece and TilePiece. Now imagine you want to animate some of the shapes on the game board. But not all shapes can be animatable, so what do you do with class inheritance? Could we create a class with an animate() method, and have only some of the GameShape subclasses inherit from that class? If we can, then we could have PlayerPiece, for example, extend both the GameShape class and Animatable class, while the TilePiece would extend only GameShape. But no, this won't work! Java supports only single inheritance! That means a class can have only one immediate superclass. In other words, if PlayerPiece is a class, there is no way to say something like this: class PlayerPiece extends GameShape, Animatable { // NO! // more code } A class cannot extend more than one class. That means one parent per class. A class can have multiple ancestors, however, since class B could extend class A, and class C could extend class B, and so on. So any given class might have multiple classes up its inheritance tree, but that's not the same as saying a class directly extends two classes. Some languages (like C++) allow a class to extend more than one other class. This capability is known as "multiple inheritance." The reason that Java's creators chose not to allow multiple inheritance is that it can become quite messy. In a nutshell, the problem is that if a class extended two other classes, and both superclasses had, say, a doStuff() method, which version of doStuff() would the subclass inherit? This issue can lead to a scenario known as the "Deadly Diamond of Death," because of the shape of the class diagram that can be created in a multiple inheritance design. The diamond is formed when classes B and C both extend A, and both B and C inherit a method from A. If class D extends both B and C, and both B and C have overridden the method in A, class D has, in theory, inherited two different implementations of the same method. Drawn as a class diagram, the shape of the four classes looks like a diamond. So if that doesn't work, what else could you do? You could simply put the animate() code in GameShape, and then disable the method in classes that can't be animated. But that's a bad design choice for many reasons, including it's more errorprone, it makes the GameShape class less cohesive (more on cohesion in a minute), and it means the GameShape API "advertises" that all shapes can be animated, when in fact that's not true since only some of the GameShape subclasses will be able to successfully run the animate() method. So what else could you do? You already know the answer—create an Animatable interface, and have only the GameShape subclasses that can be animated implement that interface. Here's the interface: public interface Animatable { public void animate(); } And here's the modified PlayerPiece class that implements the interface: 96 Chapter 2: Object Orientation Polymorphism (Exam Objective 5.2) 97 class PlayerPiece extends GameShape implements Animatable { public void movePiece() { System.out.println("moving game piece"); } public void animate() { System.out.println("animating..."); } // more code } So now we have a PlayerPiece that passes the IS-A test for both the GameShape class and the Animatable interface. That means a PlayerPiece can be treated polymorphically as one of four things at any given time, depending on the declared type of the reference variable: n An Object (since any object inherits from Object) n A GameShape (since PlayerPiece extends GameShape) n A PlayerPiece (since that's what it really is) n An Animatable (since PlayerPiece implements Animatable) The following are all legal declarations. Look closely: PlayerPiece player = new PlayerPiece(); Object o = player; GameShape shape = player; Animatable mover = player; There's only one object here—an instance of type PlayerPiece—but there are four different types of reference variables, all referring to that one object on the heap. Pop quiz: which of the preceding reference variables can invoke the display() method? Hint: only two of the four declarations can be used to invoke the display() method. Remember that method invocations allowed by the compiler are based solely on the declared type of the reference, regardless of the object type. So looking at the four reference types again—Object, GameShape, PlayerPiece, and Animatable— which of these four types know about the display() method? You guessed it—both the GameShape class and the PlayerPiece class are known (by the compiler) to have a display() method, so either of those reference types can be used to invoke display(). Remember that to the compiler, a PlayerPiece IS-A GameShape, so the compiler says, "I see that the declared type is PlayerPiece, and since PlayerPiece extends GameShape, that means PlayerPiece inherited the display() method. Therefore, PlayerPiece can be used to invoke the display() method." Which methods can be invoked when the PlayerPiece object is being referred to using a reference declared as type Animatable? Only the animate() method. Of course the cool thing here is that any class from any inheritance tree can also implement Animatable, so that means if you have a method with an argument declared as type Animatable, you can pass in PlayerPiece objects, SpinningLogo objects, and anything else that's an instance of a class that implements Animatable. And you can use that parameter (of type Animatable) to invoke the animate() method, but not the display() method (which it might not even have), or anything other than what is known to the compiler based on the reference type. The compiler always knows, though, that you can invoke the methods of class Object on any object, so those are safe to call regardless of the reference—class or interface— used to refer to the object. We've left out one big part of all this, which is that even though the compiler only knows about the declared reference type, the JVM at runtime knows what the object really is. And that means that even if the PlayerPiece object's display() method is called using a GameShape reference variable, if the PlayerPiece overrides the display() method, the JVM will invoke the PlayerPiece version! The JVM looks at the real object at the other end of the reference, "sees" that it has overridden the method of the declared reference variable type, and invokes the method of the object's actual class. But one other thing to keep in mind: Polymorphic method invocations apply only to instance methods. You can always refer to an object with a more general reference variable type (a superclass or interface), but at runtime, the ONLY things that are dynamically selected based on the actual object (rather than the reference type) are instance methods. Not static methods. Not variables. Only overridden instance methods are dynamically invoked based on the real object's type. Since this definition depends on a clear understanding of overriding, and the distinction between static methods and instance methods, we'll cover those next. 98 Chapter 2: Object Orientation CERTIFICATION OBJECTIVE Overriding / Overloading (Exam Objectives 1.5 and 5.4) 1.5 Given a code example, determine if a method is correctly overriding or overloading another method, and identify legal return values (including covariant returns), for the method. 5.4 Given a scenario, develop code that declares and/or invokes overridden or overloaded methods and code that declares and/or invokes superclass, overridden, or overloaded constructors. Overridden Methods Any time you have a class that inherits a method from a superclass, you have the opportunity to override the method (unless, as you learned earlier, the method is marked final). The key benefit of overriding is the ability to define behavior that's specific to a particular subclass type. The following example demonstrates a Horse subclass of Animal overriding the Animal version of the eat() method: public class Animal { public void eat() { System.out.println("Generic Animal Eating Generically"); } } class Horse extends Animal { public void eat() { System.out.println("Horse eating hay, oats, " + "and horse treats"); } } For abstract methods you inherit from a superclass, you have no choice. You must implement the method in the subclass unless the subclass is also abstract. Abstract methods must be implemented by the concrete subclass, but this is a lot like saying that the concrete subclass overrides the abstract methods of the superclass. So you could think of abstract methods as methods you're forced to override. Overridden Methods (Exam Objectives 1.5 and 5.4) 99 The Animal class creator might have decided that for the purposes of polymorphism, all Animal subtypes should have an eat() method defined in a unique, specific way. Polymorphically, when someone has an Animal reference that refers not to an Animal instance, but to an Animal subclass instance, the caller should be able to invoke eat() on the Animal reference, but the actual runtime object (say, a Horse instance) will run its own specific eat() method. Marking the eat() method abstract is the Animal programmer's way of saying to all subclass developers, "It doesn't make any sense for your new subtype to use a generic eat() method, so you have to come up with your own eat() method implementation!" A (non-abstract), example of using polymorphism looks like this: public class TestAnimals { public static void main (String [] args) { Animal a = new Animal(); Animal b = new Horse(); //Animal ref, but a Horse object a.eat(); // Runs the Animal version of eat() b.eat(); // Runs the Horse version of eat() } } class Animal { public void eat() { System.out.println("Generic Animal Eating Generically"); } } class Horse extends Animal { public void eat() { System.out.println("Horse eating hay, oats, " + "and horse treats"); } public void buck() { } } In the preceding code, the test class uses an Animal reference to invoke a method on a Horse object. Remember, the compiler will allow only methods in class Animal to be invoked when using a reference to an Animal. The following would not be legal given the preceding code: Animal c = new Horse(); c.buck(); // Can't invoke buck(); // Animal class doesn't have that method 100 Chapter 2: Object Orientation To reiterate, the compiler looks only at the reference type, not the instance type. Polymorphism lets you use a more abstract supertype (including an interface) reference to refer to one of its subtypes (including interface implementers). The overriding method cannot have a more restrictive access modifier than the method being overridden (for example, you can't override a method marked public and make it protected). Think about it: if the Animal class advertises a public eat() method and someone has an Animal reference (in other words, a reference declared as type Animal), that someone will assume it's safe to call eat() on the Animal reference regardless of the actual instance that the Animal reference is referring to. If a subclass were allowed to sneak in and change the access modifier on the overriding method, then suddenly at runtime—when the JVM invokes the true object's (Horse) version of the method rather than the reference type's (Animal) version—the program would die a horrible death. (Not to mention the emotional distress for the one who was betrayed by the rogue subclass.) Let's modify the polymorphic example we saw earlier in this section. public class TestAnimals { public static void main (String [] args) { Animal a = new Animal(); Animal b = new Horse(); //Animal ref, but a Horse object a.eat(); // Runs the Animal version of eat() b.eat(); // Runs the Horse version of eat() } } class Animal { public void eat() { System.out.println("Generic Animal Eating Generically"); } } class Horse extends Animal { private void eat() { // whoa! - it's private! System.out.println("Horse eating hay, oats, " + "and horse treats"); } } If this code compiled (which it doesn't), the following would fail at runtime: Animal b = new Horse(); // Animal ref, but a Horse // object , so far so good b.eat(); // Meltdown at runtime! Overridden Methods (Exam Objectives 1.5 and 5.4) 101 The variable b is of type Animal, which has a public eat() method. But remember that at runtime, Java uses virtual method invocation to dynamically select the actual version of the method that will run, based on the actual instance. An Animal reference can always refer to a Horse instance, because Horse IS-A(n) Animal. What makes that superclass reference to a subclass instance possible is that the subclass is guaranteed to be able to do everything the superclass can do. Whether the Horse instance overrides the inherited methods of Animal or simply inherits them, anyone with an Animal reference to a Horse instance is free to call all accessible Animal methods. For that reason, an overriding method must fulfill the contract of the superclass. The rules for overriding a method are as follows: n The argument list must exactly match that of the overridden method. If they don't match, you can end up with an overloaded method you didn't intend. n The return type must be the same as, or a subtype of, the return type declared in the original overridden method in the superclass. (More on this in a few pages when we discuss covariant returns.) n The access level can't be more restrictive than the overridden method's. n The access level CAN be less restrictive than that of the overridden method. n Instance methods can be overridden only if they are inherited by the subclass. A subclass within the same package as the instance's superclass can override any superclass method that is not marked private or final. A subclass in a different package can override only those non-final methods marked public or protected (since protected methods are inherited by the subclass). n The overriding method CAN throw any unchecked (runtime) exception, regardless of whether the overridden method declares the exception. (More in Chapter 5.) n The overriding method must NOT throw checked exceptions that are new or broader than those declared by the overridden method. For example, a method that declares a FileNotFoundException cannot be overridden by a method that declares a SQLException, Exception, or any other non-runtime exception unless it's a subclass of FileNotFoundException. n The overriding method can throw narrower or fewer exceptions. Just because an overridden method "takes risks" doesn't mean that the overriding subclass' exception takes the same risks. Bottom line: an overriding method doesn't 102 Chapter 2: Object Orientation have to declare any exceptions that it will never throw, regardless of what the overridden method declares. n You cannot override a method marked final. n You cannot override a method marked static. We'll look at an example in a few pages when we discuss static methods in more detail. n If a method can't be inherited, you cannot override it. Remember that overriding implies that you're reimplementing a method you inherited! For example, the following code is not legal, and even if you added an eat() method to Horse, it wouldn't be an override of Animal's eat() method. public class TestAnimals { public static void main (String [] args) { Horse h = new Horse(); h.eat(); // Not legal because Horse didn't inherit eat() } } class Animal { private void eat() { System.out.println("Generic Animal Eating Generically"); } } class Horse extends Animal { } Invoking a Superclass Version of an Overridden Method Often, you'll want to take advantage of some of the code in the superclass version of a method, yet still override it to provide some additional specific behavior. It's like saying, "Run the superclass version of the method, then come back down here and finish with my subclass additional method code." (Note that there's no requirement that the superclass version run before the subclass code.) It's easy to do in code using the keyword super as follows: public class Animal { public void eat() { } public void printYourself() { // Useful printing code goes here } } class Horse extends Animal { public void printYourself() { // Take advantage of Animal code, then add some more Overridden Methods (Exam Objectives 1.5 and 5.4) 103 super.printYourself(); // Invoke the superclass // (Animal) code // Then do Horse-specific // print work here } } Note: Using super to invoke an overridden method only applies to instance methods. (Remember, static methods can't be overridden.) 104 Chapter 2: Object Orientation If a method is overridden but you use a polymorphic (supertype) reference to refer to the subtype object with the overriding method, the compiler assumes you’re calling the supertype version of the method. If the supertype version declares a checked exception, but the overriding subtype method does not, the compiler still thinks you are calling a method that declares an exception (more in Chapter 5). Let’s take a look at an example: class Animal { public void eat() throws Exception { // throws an Exception } } class Dog2 extends Animal { public void eat() { // no Exceptions } public static void main(String [] args) { Animal a = new Dog2(); Dog2 d = new Dog2(); d.eat(); // ok a.eat(); // compiler error - // unreported exception } } This code will not compile because of the Exception declared on the Animal eat() method. This happens even though, at runtime, the eat() method used would be the Dog version, which does not declare the exception. Examples of Legal and Illegal Method Overrides Let's take a look at overriding the eat() method of Animal: public class Animal { public void eat() { } } Table 2-1 lists examples of illegal overrides of the Animal eat() method, given the preceding version of the Animal class. Overloaded Methods You're wondering what overloaded methods are doing in an OO chapter, but we've included them here since one of the things newer Java developers are most confused about are all of the subtle differences between overloaded and overridden methods. Overloaded methods let you reuse the same method name in a class, but with different arguments (and optionally, a different return type). Overloading a method often means you're being a little nicer to those who call your methods, because your code takes on the burden of coping with different argument types rather than forcing the caller to do conversions prior to invoking your method. The rules are simple: n Overloaded methods MUST change the argument list. n Overloaded methods CAN change the return type. n Overloaded methods CAN change the access modifier. n Overloaded methods CAN declare new or broader checked exceptions. Overloaded Methods (Exam Objectives 1.5 and 5.4) 105 Illegal Override Code Problem with the Code private void eat() { } Access modifier is more restrictive public void eat() throws IOException { } Declares a checked exception not defined by superclass version public void eat(String food) { } A legal overload, not an override, because the argument list changed public String eat() { } Not an override because of the return type, not an overload either because there’s no change in the argument list table 2-1 Examples of Illegal Overrides n A method can be overloaded in the same class or in a subclass. In other words, if class A defines a doStuff(int i) method, the subclass B could define a doStuff(String s) method without overriding the superclass version that takes an int. So two methods with the same name but in different classes can still be considered overloaded, if the subclass inherits one version of the method and then declares another overloaded version in its class definition. Legal Overloads Let's look at a method we want to overload: public void changeSize(int size, String name, float pattern) { } The following methods are legal overloads of the changeSize() method: public void changeSize(int size, String name) { } public int changeSize(int size, float pattern) { } public void changeSize(float pattern, String name) throws IOException { } 106 Chapter 2: Object Orientation Be careful to recognize when a method is overloaded rather than overridden. You might see a method that appears to be violating a rule for overriding, but that is actually a legal overload, as follows: public class Foo { public void doStuff(int y, String s) { } public void moreThings(int x) { } } class Bar extends Foo { public void doStuff(int y, long s) throws IOException { } } It's tempting to see the IOException as the problem, because the overridden doStuff() method doesn’t declare an exception, and IOException is checked by the compiler. But the doStuff() method is not overridden! Subclass Bar overloads the doStuff() method, by varying the argument list, so the IOException is fine. Invoking Overloaded Methods Note that there's a lot more to this discussion on how the compiler knows which method to invoke, but the rest is covered in Chapter 3 when we look at boxing and var-args—both of which have a huge impact on overloading. (You still have to pay attention to the part covered here, though.) When a method is invoked, more than one method of the same name might exist for the object type you're invoking a method on. For example, the Horse class might have three methods with the same name but with different argument lists, which means the method is overloaded. Deciding which of the matching methods to invoke is based on the arguments. If you invoke the method with a String argument, the overloaded version that takes a String is called. If you invoke a method of the same name but pass it a float, the overloaded version that takes a float will run. If you invoke the method of the same name but pass it a Foo object, and there isn't an overloaded version that takes a Foo, then the compiler will complain that it can't find a match. The following are examples of invoking overloaded methods: class Adder { public int addThem(int x, int y) { return x + y; } // Overload the addThem method to add doubles instead of ints public double addThem(double x, double y) { return x + y; } } // From another class, invoke the addThem() method public class TestAdder { public static void main (String [] args) { Adder a = new Adder(); int b = 27; int c = 3; int result = a.addThem(b,c); // Which addThem is invoked? double doubleResult = a.addThem(22.5,9.3); // Which addThem? } } In the preceding TestAdder code, the first call to a.addThem(b,c) passes two ints to the method, so the first version of addThem()—the overloaded version Overloaded Methods (Exam Objectives 1.5 and 5.4) 107 that takes two int arguments—is called. The second call to a.addThem(22.5, 9.3) passes two doubles to the method, so the second version of addThem()—the overloaded version that takes two double arguments—is called. Invoking overloaded methods that take object references rather than primitives is a little more interesting. Say you have an overloaded method such that one version takes an Animal and one takes a Horse (subclass of Animal). If you pass a Horse object in the method invocation, you'll invoke the overloaded version that takes a Horse. Or so it looks at first glance: class Animal { } class Horse extends Animal { } class UseAnimals { public void doStuff(Animal a) { System.out.println("In the Animal version"); } public void doStuff(Horse h) { System.out.println("In the Horse version"); } public static void main (String [] args) { UseAnimals ua = new UseAnimals(); Animal animalObj = new Animal(); Horse horseObj = new Horse(); ua.doStuff(animalObj); ua.doStuff(horseObj); } } The output is what you expect: in the Animal version in the Horse version But what if you use an Animal reference to a Horse object? Animal animalRefToHorse = new Horse(); ua.doStuff(animalRefToHorse); Which of the overloaded versions is invoked? You might want to say, "The one that takes a Horse, since it's a Horse object at runtime that's being passed to the method." But that's not how it works. The preceding code would actually print: in the Animal version 108 Chapter 2: Object Orientation Even though the actual object at runtime is a Horse and not an Animal, the choice of which overloaded method to call (in other words, the signature of the method) is NOT dynamically decided at runtime. Just remember, the reference type (not the object type) determines which overloaded method is invoked! To summarize, which overridden version of the method to call (in other words, from which class in the inheritance tree) is decided at runtime based on object type, but which overloaded version of the method to call is based on the reference type of the argument passed at compile time. If you invoke a method passing it an Animal reference to a Horse object, the compiler knows only about the Animal, so it chooses the overloaded version of the method that takes an Animal. It does not matter that at runtime there's actually a Horse being passed. Polymorphism in Overloaded and Overridden Methods How does polymorphism work with overloaded methods? From what we just looked at, it doesn't appear that polymorphism matters when a method is overloaded. If you pass an Animal reference, the overloaded method that takes an Animal will be invoked, even if the actual object passed is a Horse. Once the Horse masquerading as Animal gets in to the method, however, the Horse object is still a Horse despite being passed into a method expecting an Animal. So it's true that polymorphism doesn't determine which overloaded version is called; polymorphism does come into play when the decision is about which overridden version of a method is called. But sometimes, a method is both overloaded and overridden. Imagine the Animal and Horse classes look like this: public class Animal { public void eat() { System.out.println("Generic Animal Eating Generically"); } } public class Horse extends Animal { public void eat() { System.out.println("Horse eating hay "); } public void eat(String s) { System.out.println("Horse eating " + s); } } Notice that the Horse class has both overloaded and overridden the eat() method. Table 2-2 shows which version of the three eat() methods will run depending on how they are invoked. Overloaded Methods (Exam Objectives 1.5 and 5.4) 109 110 Chapter 2: Object Orientation Method Invocation Code Result Animal a = new Animal(); a.eat(); Generic Animal Eating Generically Horse h = new Horse(); h.eat(); Horse eating hay Animal ah = new Horse(); ah.eat(); Horse eating hay Polymorphism works—the actual object type (Horse), not the reference type (Animal), is used to determine which eat() is called. Horse he = new Horse(); he.eat("Apples"); Horse eating Apples The overloaded eat(String s) method is invoked. Animal a2 = new Animal(); a2.eat("treats"); Compiler error! Compiler sees that Animal class doesn't have an eat() method that takes a String. Animal ah2 = new Horse(); ah2.eat("Carrots"); Compiler error! Compiler still looks only at the reference, and sees that Animal doesn’t have an eat() method that takes a String. Compiler doesn’t care that the actual object might be a Horse at runtime. table 2-2 Examples of Illegal Overrides Don’t be fooled by a method that’s overloaded but not overridden by a subclass. It’s perfectly legal to do the following: public class Foo { void doStuff() { } } class Bar extends Foo { void doStuff(String s) { } } The Bar class has two doStuff() methods: the no-arg version it inherits from Foo (and does not override), and the overloaded doStuff(String s) defined in the Bar class. Code with a reference to a Foo can invoke only the no-arg version, but code with a reference to a Bar can invoke either of the overloaded versions. Table 2-3 summarizes the difference between overloaded and overridden methods. The current objective (5.4) covers both method and constructor overloading, but we'll cover constructor overloading in the next section, where we'll also cover the other constructor-related topics that are on the exam. Figure 2-4 illustrates the way overloaded and overridden methods appear in class relationships. FIGURE 2-4 Overloaded and overridden methods in class relationships Overloaded Methods (Exam Objectives 1.5 and 5.4) 111 Overloaded Method Overridden Method Argument(s) Must change. Must not change. Return type Can change. Can’t change except for covariant returns. Exceptions Can change. Can reduce or eliminate. Must not throw new or broader checked exceptions. Access Can change. Must not make more restrictive (can be less restrictive). Invocation Reference type determines which overloaded version (based on declared argument types) is selected. Happens at compile time. The actual method that’s invoked is still a virtual method invocation that happens at runtime, but the compiler will already know the signature of the method to be invoked. So at runtime, the argument match will already have been nailed down, just not the class in which the method lives. Object type (in other words, the type of the actual instance on the heap) determines which method is selected. Happens at runtime. table 2-3 Differences Between Overloaded and Overridden Methods CERTIFICATION OBJECTIVE Reference Variable Casting (Objective 5.2) 5.2 Given a scenario, develop code that demonstrates the use of polymorphism. Further, determine when casting will be necessary and recognize compiler vs. runtime errors related to object reference casting. We've seen how it's both possible and common to use generic reference variable types to refer to more specific object types. It's at the heart of polymorphism. For example, this line of code should be second nature by now: Animal animal = new Dog(); But what happens when you want to use that animal reference variable to invoke a method that only class Dog has? You know it's referring to a Dog, and you want to do a Dog-specific thing? In the following code, we've got an array of Animals, and whenever we find a Dog in the array, we want to do a special Dog thing. Let's agree for now that all of this code is OK, except that we're not sure about the line of code that invokes the playDead method. class Animal { void makeNoise() {System.out.println("generic noise"); } } class Dog extends Animal { void makeNoise() {System.out.println("bark"); } void playDead() { System.out.println(" roll over"); } } class CastTest2 { public static void main(String [] args) { Animal [] a = {new Animal(), new Dog(), new Animal() }; for(Animal animal : a) { animal.makeNoise(); if(animal instanceof Dog) { animal.playDead(); // try to do a Dog behavior ? } } } } 112 Chapter 2: Object Orientation When we try to compile this code, the compiler says something like this: cannot find symbol The compiler is saying, "Hey, class Animal doesn't have a playDead() method". Let's modify the if code block: if(animal instanceof Dog) { Dog d = (Dog) animal; // casting the ref. var. d.playDead(); } The new and improved code block contains a cast, which in this case is sometimes called a downcast, because we're casting down the inheritance tree to a more specific class. Now, the compiler is happy. Before we try to invoke playDead, we cast the animal variable to type Dog. What we're saying to the compiler is, "We know it's really referring to a Dog object, so it's okay to make a new Dog reference variable to refer to that object." In this case we're safe because before we ever try the cast, we do an instanceof test to make sure. It's important to know that the compiler is forced to trust us when we do a downcast, even when we screw up: class Animal { } class Dog extends Animal { } class DogTest { public static void main(String [] args) { Animal animal = new Animal(); Dog d = (Dog) animal; // compiles but fails later } } It can be maddening! This code compiles! When we try to run it, we'll get an exception something like this: java.lang.ClassCastException Why can't we trust the compiler to help us out here? Can't it see that animal is of type Animal? All the compiler can do is verify that the two types are in the same inheritance tree, so that depending on whatever code might have come before the downcast, it's possible that animal is of type Dog. The compiler must allow Reference Variable Casting (Objective 5.2) 113 things that might possibly work at runtime. However, if the compiler knows with certainty that the cast could not possibly work, compilation will fail. The following replacement code block will NOT compile: Animal animal = new Animal(); Dog d = (Dog) animal; String s = (String) animal; // animal can't EVER be a String In this case, you'll get an error something like this: inconvertible types Unlike downcasting, upcasting (casting up the inheritance tree to a more general type) works implicitly (i.e. you don't have to type in the cast) because when you upcast you're implicitly restricting the number of methods you can invoke, as opposed to downcasting, which implies that later on, you might want to invoke a more specific method. For instance: class Animal { } class Dog extends Animal { } class DogTest { public static void main(String [] args) { Dog d = new Dog(); Animal a1 = d; // upcast ok with no explicit cast Animal a2 = (Animal) d; // upcast ok with an explicit cast } } Both of the previous upcasts will compile and run without exception, because a Dog IS-A Animal, which means that anything an Animal can do, a Dog can do. A Dog can do more, of course, but the point is—anyone with an Animal reference can safely call Animal methods on a Dog instance. The Animal methods may have been overridden in the Dog class, but all we care about now is that a Dog can always do at least everything an Animal can do. The compiler and JVM know it too, so the implicit upcast is always legal for assigning an object of a subtype to a reference of one of its supertype classes (or interfaces). If Dog implements Pet, and Pet defines beFriendly(), then a Dog can be implicitly cast to a Pet, but the only Dog method you can invoke then is beFriendly(), which Dog was forced to implement because Dog implements the Pet interface. 114 Chapter 2: Object Orientation One more thing…if Dog implements Pet, then if Beagle extends Dog, but Beagle does not declare that it implements Pet, Beagle is still a Pet! Beagle is a Pet simply because it extends Dog, and Dog's already taken care of the Pet parts of itself, and all its children. The Beagle class can always override any methods it inherits from Dog, including methods that Dog implemented to fulfill its interface contract. And just one more thing…if Beagle does declare it implements Pet, just so that others looking at the Beagle class API can easily see that Beagle IS-A Pet, without having to look at Beagle's superclasses, Beagle still doesn't need to implement the beFriendly() method if the Dog class (Beagle's superclass) has already taken care of that. In other words, if Beagle IS-A Dog, and Dog IS-A Pet, then Beagle IS-A Pet, and has already met its Pet obligations for implementing the beFriendly() method since it inherits the beFriendly() method. The compiler is smart enough to say, "I know Beagle already IS a Dog, but it's OK to make it more obvious." So don't be fooled by code that shows a concrete class that declares that it implements an interface, but doesn't implement the methods of the interface. Before you can tell whether the code is legal, you must know what the superclasses of this implementing class have declared. If any class in its inheritance tree has already provided concrete (i.e., non-abstract) method implementations, and has declared that it (the superclass) implements the interface, then the subclass is under no obligation to re-implement (override) those methods. Reference Variable Casting (Objective 5.2) 115 The exam creators will tell you that they’re forced to jam tons of code into little spaces "because of the exam engine." While that’s partially true, they ALSO like to obfuscate. The following code: Animal a = new Dog(); Dog d = (Dog) a; a.doDogStuff(); Can be replaced with this easy-to-read bit of fun: Animal a = new Dog(); ((Dog)a).doDogStuff(); In this case the compiler needs all of those parentheses, otherwise it thinks it’s been handed an incomplete statement. CERTIFICATION OBJECTIVE Implementing an Interface (Exam Objective 1.2) 1.2 Develop code that declares an interface... When you implement an interface, you're agreeing to adhere to the contract defined in the interface. That means you're agreeing to provide legal implementations for every method defined in the interface, and that anyone who knows what the interface methods look like (not how they're implemented, but how they can be called and what they return) can rest assured that they can invoke those methods on an instance of your implementing class. For example, if you create a class that implements the Runnable interface (so that your code can be executed by a specific thread), you must provide the public void run() method. Otherwise, the poor thread could be told to go execute your Runnable object's code and—surprise surprise—the thread then discovers the object has no run() method! (At which point, the thread would blow up and the JVM would crash in a spectacular yet horrible explosion.) Thankfully, Java prevents this meltdown from occurring by running a compiler check on any class that claims to implement an interface. If the class says it's implementing an interface, it darn well better have an implementation for each method in the interface (with a few exceptions we'll look at in a moment). Assuming an interface, Bounceable, with two methods: bounce(), and setBounceFactor(), the following class will compile: public class Ball implements Bounceable { // Keyword // 'implements' public void bounce() { } public void setBounceFactor(int bf) { } } OK, we know what you're thinking: "This has got to be the worst implementation class in the history of implementation classes." It compiles, though. And runs. The interface contract guarantees that a class will have the method (in other words, others can call the method subject to access control), but it never guaranteed a good implementation—or even any actual implementation code in the body of the method. The compiler will never say to you, "Um, excuse me, but did you really 116 Chapter 2: Object Orientation mean to put nothing between those curly braces? HELLO. This is a method after all, so shouldn't it do something?" Implementation classes must adhere to the same rules for method implementation as a class extending an abstract class. In order to be a legal implementation class, a nonabstract implementation class must do the following: n Provide concrete (nonabstract) implementations for all methods from the declared interface. n Follow all the rules for legal overrides. n Declare no checked exceptions on implementation methods other than those declared by the interface method, or subclasses of those declared by the interface method. n Maintain the signature of the interface method, and maintain the same return type (or a subtype). (But it does not have to declare the exceptions declared in the interface method declaration.) But wait, there's more! An implementation class can itself be abstract! For example, the following is legal for a class Ball implementing Bounceable: abstract class Ball implements Bounceable { } Notice anything missing? We never provided the implementation methods. And that's OK. If the implementation class is abstract, it can simply pass the buck to its first concrete subclass. For example, if class BeachBall extends Ball, and BeachBall is not abstract, then BeachBall will have to provide all the methods from Bounceable: class BeachBall extends Ball { // Even though we don't say it in the class declaration above, // BeachBall implements Bounceable, since BeachBall's abstract // superclass (Ball) implements Bounceable public void bounce() { // interesting BeachBall-specific bounce code } public void setBounceFactor(int bf) { // clever BeachBall-specific code for setting // a bounce factor } Implementing an Interface (Exam Objective 1.2) 117 // if class Ball defined any abstract methods, // they'll have to be // implemented here as well. } Look for classes that claim to implement an interface but don't provide the correct method implementations. Unless the implementing class is abstract, the implementing class must provide implementations for all methods defined in the interface. Two more rules you need to know and then we can put this topic to sleep (or put you to sleep; we always get those two confused): 1. A class can implement more than one interface. It's perfectly legal to say, for example, the following: public class Ball implements Bounceable, Serializable, Runnable { ... } You can extend only one class, but implement many interfaces. But remember that subclassing defines who and what you are, whereas implementing defines a role you can play or a hat you can wear, despite how different you might be from some other class implementing the same interface (but from a different inheritance tree). For example, a Person extends HumanBeing (although for some, that's debatable). But a Person may also implement Programmer, Snowboarder, Employee, Parent, or PersonCrazyEnoughToTakeThisExam. 2. An interface can itself extend another interface, but never implement anything. The following code is perfectly legal: public interface Bounceable extends Moveable { } // ok! What does that mean? The first concrete (nonabstract) implementation class of Bounceable must implement all the methods of Bounceable, plus all the methods of Moveable! The subinterface, as we call it, simply adds more requirements to the contract of the superinterface. You'll see this concept applied in many areas of Java, especially J2EE where you'll often have to build your own interface that extends one of the J2EE interfaces. 118 Chapter 2: Object Orientation Hold on though, because here's where it gets strange. An interface can extend more than one interface! Think about that for a moment. You know that when we're talking about classes, the following is illegal: public class Programmer extends Employee, Geek { } // Illegal! As we mentioned earlier, a class is not allowed to extend multiple classes in Java. An interface, however, is free to extend multiple interfaces. interface Bounceable extends Moveable, Spherical { // ok! void bounce(); void setBounceFactor(int bf); } interface Moveable { void moveIt(); } interface Spherical { void doSphericalThing(); } In the next example, Ball is required to implement Bounceable, plus all methods from the interfaces that Bounceable extends (including any interfaces those interfaces extend, and so on until you reach the top of the stack—or is it the bottom of the stack?). So Ball would need to look like the following: class Ball implements Bounceable { public void bounce() { } // Implement Bounceable's methods public void setBounceFactor(int bf) { } public void moveIt() { } // Implement Moveable's method public void doSphericalThing() { } // Implement Spherical } If class Ball fails to implement any of the methods from Bounceable, Moveable, or Spherical, the compiler will jump up and down wildly, red in the face, until it does. Unless, that is, class Ball is marked abstract. In that case, Ball could choose to implement any, all, or none of the methods from any of the interfaces, thus leaving the rest of the implementations to a concrete subclass of Ball, as follows: Implementing an Interface (Exam Objective 1.2) 119 abstract class Ball implements Bounceable { public void bounce() { ... } // Define bounce behavior public void setBounceFactor(int bf) { ... } // Don't implement the rest; leave it for a subclass } class SoccerBall extends Ball { // class SoccerBall must // implement the interface methods that Ball didn't public void moveIt() { ... } public void doSphericalThing() { ... } // SoccerBall can choose to override the Bounceable methods // implemented by Ball public void bounce() { ... } } Figure 2-5 compares concrete and abstract examples of extends and implements, for both classes and interfaces. 120 Chapter 2: Object Orientation Figure 2-5 Comparing Concrete and Abstract Examples of Extends and Implements Implementing an Interface (Exam Objective 1.2) 121 Look for illegal uses of extends and implements. The following shows examples of legal and illegal class and interface declarations: class Foo { } // OK class Bar implements Foo { } // No! Can't implement a class interface Baz { } // OK interface Fi { } // OK interface Fee implements Baz { } // No! Interface can't // implement an interface interface Zee implements Foo { } // No! Interface can't // implement a class interface Zoo extends Foo { } // No! Interface can't // extend a class interface Boo extends Fi { } // OK. Interface can extend // an interface class Toon extends Foo, Button { } // No! Class can't extend // multiple classes class Zoom implements Fi, Fee { } // OK. class can implement // multiple interfaces interface Vroom extends Fi, Fee { } // OK. interface can extend // multiple interfaces class Yow extends Foo implements Fi { } // OK. Class can do both // (extends must be 1st) Burn these in, and watch for abuses in the questions you get on the exam. Regardless of what the question appears to be testing, the real problem might be the class or interface declaration. Before you get caught up in, say, tracing a complex threading flow, check to see if the code will even compile. (Just that tip alone may be worth your putting us in your will!) (You’ll be impressed by the effort the exam developers put into distracting you from the real problem.) (How did people manage to write anything before parentheses were invented?) CERTIFICATION OBJECTIVE Legal Return Types (Exam Objective 1.5) 1.5 Given a code example, determine if a method is correctly overriding or overloading another method, and identify legal return values (including covariant returns), for the method. This objective covers two aspects of return types: what you can declare as a return type, and what you can actually return as a value. What you can and cannot declare is pretty straightforward, but it all depends on whether you're overriding an inherited method or simply declaring a new method (which includes overloaded methods). We'll take just a quick look at the difference between return type rules for overloaded and overriding methods, because we've already covered that in this chapter. We'll cover a small bit of new ground, though, when we look at polymorphic return types and the rules for what is and is not legal to actually return. Return Type Declarations This section looks at what you're allowed to declare as a return type, which depends primarily on whether you are overriding, overloading, or declaring a new method. Return Types on Overloaded Methods Remember that method overloading is not much more than name reuse. The overloaded method is a completely different method from any other method of the same name. So if you inherit a method but overload it in a subclass, you're not subject to the restrictions of overriding, which means you can declare any return type you like. What you can't do is change only the return type. To overload a method, remember, you must change the argument list. The following code shows an overloaded method: public class Foo{ void go() { } } public class Bar extends Foo { String go(int x) { 122 Chapter 2: Object Orientation return null; } } Notice that the Bar version of the method uses a different return type. That's perfectly fine. As long as you've changed the argument list, you're overloading the method, so the return type doesn't have to match that of the superclass version. What you're NOT allowed to do is this: public class Foo{ void go() { } } public class Bar extends Foo { String go() { // Not legal! Can't change only the return type return null; } } Overriding and Return Types, and Covariant Returns When a subclass wants to change the method implementation of an inherited method (an override), the subclass must define a method that matches the inherited version exactly. Or, as of Java 5, you're allowed to change the return type in the overriding method as long as the new return type is a subtype of the declared return type of the overridden (superclass) method. Let's look at a covariant return in action: class Alpha { Alpha doStuff(char c) { return new Alpha(); } } class Beta extends Alpha { Beta doStuff(char c) { // legal override in Java 1.5 return new Beta(); } } As of Java 5, this code will compile. If you were to attempt to compile this code with a 1.4 compiler or with the source flag as follows: Return Type Declarations (Exam Objective 1.5) 123 javac -source 1.4 Beta.java you would get a compiler error something like this: attempting to use incompatible return type (We'll talk more about compiler flags in Chapter 10.) Other rules apply to overriding, including those for access modifiers and declared exceptions, but those rules aren't relevant to the return type discussion. For the exam, be sure you know that overloaded methods can change the return type, but overriding methods can do so only within the bounds of covariant returns. Just that knowledge alone will help you through a wide range of exam questions. Returning a Value You have to remember only six rules for returning a value: 1. You can return null in a method with an object reference return type. public Button doStuff() { return null; } 2. An array is a perfectly legal return type. public String[] go() { return new String[] {"Fred", "Barney", "Wilma"}; } 3. In a method with a primitive return type, you can return any value or variable that can be implicitly converted to the declared return type. public int foo() { char c = 'c'; return c; // char is compatible with int } 124 Chapter 2: Object Orientation 4. In a method with a primitive return type, you can return any value or variable that can be explicitly cast to the declared return type. public int foo () { float f = 32.5f; return (int) f; } 5. You must not return anything from a method with a void return type. public void bar() { return "this is it"; // Not legal!! } 6. In a method with an object reference return type, you can return any object type that can be implicitly cast to the declared return type. public Animal getAnimal() { return new Horse(); // Assume Horse extends Animal } public Object getObject() { int[] nums = {1,2,3}; return nums; // Return an int array, // which is still an object } public interface Chewable { } public class Gum implements Chewable { } public class TestChewable { // Method with an interface return type public Chewable getChewable() { return new Gum(); // Return interface implementer } } Returning a Value (Exam Objective 1.5) 125 CERTIFICATION OBJECTIVE Constructors and Instantiation (Exam Objectives 1.6 and 5.4) 1.6 Given a set of classes and superclasses, develop constructors for one or more of the classes. Given a class declaration, determine if a default constructor will be created, and if so, determine the behavior of that constructor. Given a nested or non-nested class listing, write code to instantiate the class. 5.4 Given a scenario, develop code that declares and/or invokes overridden or overloaded methods and code that declares and/or invokes superclass, overridden, or overloaded constructors. 126 Chapter 2: Object Orientation Watch for methods that declare an abstract class or interface return type, and know that any object that passes the IS-A test (in other words, would test true using the instanceof operator) can be returned from that method— for example: public abstract class Animal { } public class Bear extends Animal { } public class Test { public Animal go() { return new Bear(); // OK, Bear "is-a" Animal } } This code will compile, the return value is a subtype. Objects are constructed. You can't make a new object without invoking a constructor. In fact, you can't make a new object without invoking not just the constructor of the object's actual class type, but also the constructor of each of its superclasses! Constructors are the code that runs whenever you use the keyword new. OK, to be a bit more accurate, there can also be initialization blocks that run when you say new, but we're going to cover them (init blocks), and their static initialization counterparts, in the next chapter. We've got plenty to talk about here—we'll look at how constructors are coded, who codes them, and how they work at runtime. So grab your hardhat and a hammer, and let's do some object building. Constructor Basics Every class, including abstract classes, MUST have a constructor. Burn that into your brain. But just because a class must have one, doesn't mean the programmer has to type it. A constructor looks like this: class Foo { Foo() { } // The constructor for the Foo class } Notice what's missing? There's no return type! Two key points to remember about constructors are that they have no return type and their names must exactly match the class name. Typically, constructors are used to initialize instance variable state, as follows: class Foo { int size; String name; Foo(String name, int size) { this.name = name; this.size = size; } } In the preceding code example, the Foo class does not have a no-arg constructor. That means the following will fail to compile: Foo f = new Foo(); // Won't compile, no matching constructor but the following will compile: Constructors and Instantiation (Exam Objectives 1.6 & 5.4) 127 Foo f = new Foo("Fred", 43); // No problem. Arguments match // the Foo constructor. So it's very common (and desirable) for a class to have a no-arg constructor, regardless of how many other overloaded constructors are in the class (yes, constructors can be overloaded). You can't always make that work for your classes; occasionally you have a class where it makes no sense to create an instance without supplying information to the constructor. A java.awt.Color object, for example, can't be created by calling a no-arg constructor, because that would be like saying to the JVM, "Make me a new Color object, and I really don't care what color it is...you pick." Do you seriously want the JVM making your style decisions? Constructor Chaining We know that constructors are invoked at runtime when you say new on some class type as follows: Horse h = new Horse(); But what really happens when you say new Horse() ? (Assume Horse extends Animal and Animal extends Object.) 1. Horse constructor is invoked. Every constructor invokes the constructor of its superclass with an (implicit) call to super(), unless the constructor invokes an overloaded constructor of the same class (more on that in a minute). 2. Animal constructor is invoked (Animal is the superclass of Horse). 3. Object constructor is invoked (Object is the ultimate superclass of all classes, so class Animal extends Object even though you don't actually type "extends Object" into the Animal class declaration. It's implicit.) At this point we're on the top of the stack. 4. Object instance variables are given their explicit values. By explicit values, we mean values that are assigned at the time the variables are declared, like "int x = 27", where "27" is the explicit value (as opposed to the default value) of the instance variable. 5. Object constructor completes. 6. Animal instance variables are given their explicit values (if any). 7. Animal constructor completes. 128 Chapter 2: Object Orientation 8. Horse instance variables are given their explicit values (if any). 9. Horse constructor completes. Figure 2-6 shows how constructors work on the call stack. FIGURE 2-6 Constructors on the call stack Rules for Constructors The following list summarizes the rules you'll need to know for the exam (and to understand the rest of this section). You MUST remember these, so be sure to study them more than once. n Constructors can use any access modifier, including private. (A private constructor means only code within the class itself can instantiate an object of that type, so if the private constructor class wants to allow an instance of the class to be used, the class must provide a static method or variable that allows access to an instance created from within the class.) n The constructor name must match the name of the class. n Constructors must not have a return type. n It's legal (but stupid) to have a method with the same name as the class, but that doesn't make it a constructor. If you see a return type, it's a method rather than a constructor. In fact, you could have both a method and a constructor with the same name—the name of the class—in the same class, and that's not a problem for Java. Be careful not to mistake a method for a constructor—be sure to look for a return type. n If you don't type a constructor into your class code, a default constructor will be automatically generated by the compiler. n The default constructor is ALWAYS a no-arg constructor. n If you want a no-arg constructor and you've typed any other constructor(s) into your class code, the compiler won't provide the no-arg constructor (or Constructors and Instantiation (Exam Objectives 1.6 & 5.4) 129 any other constructor) for you. In other words, if you've typed in a constructor with arguments, you won't have a no-arg constructor unless you type it in yourself ! n Every constructor has, as its first statement, either a call to an overloaded constructor (this()) or a call to the superclass constructor (super()), although remember that this call can be inserted by the compiler. n If you do type in a constructor (as opposed to relying on the compiler-generated default constructor), and you do not type in the call to super() or a call to this(), the compiler will insert a no-arg call to super() for you, as the very first statement in the constructor. n A call to super() can be either a no-arg call or can include arguments passed to the super constructor. n A no-arg constructor is not necessarily the default (i.e., compiler-supplied) constructor, although the default constructor is always a no-arg constructor. The default constructor is the one the compiler provides! While the default constructor is always a no-arg constructor, you're free to put in your own noarg constructor. n You cannot make a call to an instance method, or access an instance variable, until after the super constructor runs. n Only static variables and methods can be accessed as part of the call to super() or this(). (Example: super(Animal.NAME) is OK, because NAME is declared as a static variable.) n Abstract classes have constructors, and those constructors are always called when a concrete subclass is instantiated. n Interfaces do not have constructors. Interfaces are not part of an object's inheritance tree. n The only way a constructor can be invoked is from within another constructor. In other words, you can't write code that actually calls a constructor as follows: class Horse { Horse() { } // constructor void doStuff() { Horse(); // calling the constructor - illegal! } } 130 Chapter 2: Object Orientation Determine Whether a Default Constructor Will Be Created The following example shows a Horse class with two constructors: class Horse { Horse() { } Horse(String name) { } } Will the compiler put in a default constructor for the class above? No! How about for the following variation of the class? class Horse { Horse(String name) { } } Now will the compiler insert a default constructor? No! What about this class? class Horse { } Now we're talking. The compiler will generate a default constructor for the preceding class, because the class doesn't have any constructors defined. OK, what about this class? class Horse { void Horse() { } } It might look like the compiler won't create one, since there already is a constructor in the Horse class. Or is there? Take another look at the preceding Horse class. What's wrong with the Horse() constructor? It isn't a constructor at all! It's simply a method that happens to have the same name as the class. Remember, the return type is a dead giveaway that we're looking at a method, and not a constructor. How do you know for sure whether a default constructor will be created? Because you didn't write any constructors in your class. Determine Whether a Default Constructor Will Be Created (Exam Objective 1.6) 131 How do you know what the default constructor will look like? Because... n The default constructor has the same access modifier as the class. n The default constructor has no arguments. n The default constructor includes a no-arg call to the super constructor (super()). Table 2-4 shows what the compiler will (or won't) generate for your class. What happens if the super constructor has arguments? Constructors can have arguments just as methods can, and if you try to invoke a method that takes, say, an int, but you don't pass anything to the method, the compiler will complain as follows: class Bar { void takeInt(int x) { } } class UseBar { public static void main (String [] args) { Bar b = new Bar(); b.takeInt(); // Try to invoke a no-arg takeInt() method } } The compiler will complain that you can't invoke takeInt() without passing an int. Of course, the compiler enjoys the occasional riddle, so the message it spits out on some versions of the JVM (your mileage may vary) is less than obvious: UseBar.java:7: takeInt(int) in Bar cannot be applied to () b.takeInt(); ^ But you get the idea. The bottom line is that there must be a match for the method. And by match, we mean that the argument types must be able to accept the values or variables you're passing, and in the order you're passing them. Which brings us back to constructors (and here you were thinking we'd never get there), which work exactly the same way. 132 Chapter 2: Object Orientation So if your super constructor (that is, the constructor of your immediate superclass/parent) has arguments, you must type in the call to super(), supplying the appropriate arguments. Crucial point: if your superclass does not have a no-arg Determine Whether a Default Constructor Will Be Created (Exam Objective 1.6) 133 Class Code (What You Type) Compiler Generated Constructor Code (in Bold) class Foo { } class Foo { Foo() { super(); } } class Foo { Foo() { } } class Foo { Foo() { super(); } } public class Foo { } class Foo { public Foo() { super(); } } class Foo { Foo(String s) { } } class Foo { Foo(String s) { super(); } } class Foo { Foo(String s) { super(); } } Nothing, compiler doesn’t need to insert anything. class Foo { void Foo() { } } class Foo { void Foo() { } Foo() { super(); } } (void Foo() is a method, not a constructor.) table 2-4 Compiler-Generated Constructor Code constructor, you must type a constructor in your class (the subclass) because you need a place to put in the call to super with the appropriate arguments. The following is an example of the problem: class Animal { Animal(String name) { } } class Horse extends Animal { Horse() { super(); // Problem! } } And once again the compiler treats us with the stunningly lucid: Horse.java:7: cannot resolve symbol symbol : constructor Animal () location: class Animal super(); // Problem! ^ If you're lucky (and it's a full moon), your compiler might be a little more explicit. But again, the problem is that there just isn't a match for what we're trying to invoke with super()—an Animal constructor with no arguments. Another way to put this is that if your superclass does not have a no-arg constructor, then in your subclass you will not be able to use the default constructor supplied by the compiler. It's that simple. Because the compiler can only put in a call to a no-arg super(), you won't even be able to compile something like this: class Clothing { Clothing(String s) { } } class TShirt extends Clothing { } Trying to compile this code gives us exactly the same error we got when we put a constructor in the subclass with a call to the no-arg version of super(): Clothing.java:4: cannot resolve symbol symbol : constructor Clothing () location: class Clothing 134 Chapter 2: Object Orientation class TShirt extends Clothing { } ^ In fact, the preceding Clothing and TShirt code is implicitly the same as the following code, where we've supplied a constructor for TShirt that's identical to the default constructor supplied by the compiler: class Clothing { Clothing(String s) { } } class TShirt extends Clothing { // Constructor identical to compiler-supplied // default constructor TShirt() { super(); // Won't work! } // Invokes a no-arg Clothing() constructor, } // but there isn't one! One last point on the whole default constructor thing (and it's probably very obvious, but we have to say it or we'll feel guilty for years), constructors are never inherited. They aren't methods. They can't be overridden (because they aren't methods and only instance methods can be overridden). So the type of constructor(s) your superclass has in no way determines the type of default constructor you'll get. Some folks mistakenly believe that the default constructor somehow matches the super constructor, either by the arguments the default constructor will have (remember, the default constructor is always a no-arg), or by the arguments used in the compiler-supplied call to super(). So, although constructors can't be overridden, you've already seen that they can be overloaded, and typically are. Overloaded Constructors Overloading a constructor means typing in multiple versions of the constructor, each having a different argument list, like the following examples: class Foo { Foo() { } Foo(String s) { } } Overloaded Constructors (Exam Objective 5.4) 135 The preceding Foo class has two overloaded constructors, one that takes a string, and one with no arguments. Because there's no code in the no-arg version, it's actually identical to the default constructor the compiler supplies, but remember— since there's already a constructor in this class (the one that takes a string), the compiler won't supply a default constructor. If you want a no-arg constructor to overload the with-args version you already have, you're going to have to type it yourself, just as in the Foo example. Overloading a constructor is typically used to provide alternate ways for clients to instantiate objects of your class. For example, if a client knows the animal name, they can pass that to an Animal constructor that takes a string. But if they don't know the name, the client can call the no-arg constructor and that constructor can supply a default name. Here's what it looks like: 1. public class Animal { 2. String name; 3. Animal(String name) { 4. this.name = name; 5. } 6. 7. Animal() { 8. this(makeRandomName()); 9. } 10. 11. static String makeRandomName() { 12. int x = (int) (Math.random() * 5); 13. String name = new String[] {"Fluffy", "Fido", "Rover", "Spike", "Gigi"}[x]; 14. return name; 15. } 16. 17. public static void main (String [] args) { 18. Animal a = new Animal(); 19. System.out.println(a.name); 20. Animal b = new Animal("Zeus"); 21. System.out.println(b.name); 22. } 23. } Running the code four times produces this output: 136 Chapter 2: Object Orientation % java Animal Gigi Zeus % java Animal Fluffy Zeus % java Animal Rover Zeus % java Animal Fluffy Zeus There's a lot going on in the preceding code. Figure 2-7 shows the call stack for constructor invocations when a constructor is overloaded. Take a look at the call stack, and then let's walk through the code straight from the top. FIGURE 2-7 Overloaded constructors on the call stack n Line 2 Declare a String instance variable name. n Lines 3–5 Constructor that takes a String, and assigns it to instance variable name. n Line 7 Here's where it gets fun. Assume every animal needs a name, but the client (calling code) might not always know what the name should be, so you'll assign a random name. The no-arg constructor generates a name by invoking the makeRandomName() method. n Line 8 The no-arg constructor invokes its own overloaded constructor that takes a String, in effect calling it the same way it would be called if Overloaded Constructors (Exam Objectives 1.6 & 5.4) 137 client code were doing a new to instantiate an object, passing it a String for the name. The overloaded invocation uses the keyword this, but uses it as though it were a method name, this(). So line 8 is simply calling the constructor on line 3, passing it a randomly selected String rather than a clientcode chosen name. n Line 11 Notice that the makeRandomName() method is marked static! That's because you cannot invoke an instance (in other words, nonstatic) method (or access an instance variable) until after the super constructor has run. And since the super constructor will be invoked from the constructor on line 3, rather than from the one on line 7, line 8 can use only a static method to generate the name. If we wanted all animals not specifically named by the caller to have the same default name, say, "Fred," then line 8 could have read this("Fred"); rather than calling a method that returns a string with the randomly chosen name. n Line 12 This doesn't have anything to do with constructors, but since we're all here to learn...it generates a random integer between 0 and 4. n Line 13 Weird syntax, we know. We're creating a new String object (just a single String instance), but we want the string to be selected randomly from a list. Except we don't have the list, so we need to make it. So in that one line of code we 1. Declare a String variable, name. 2. Create a String array (anonymously—we don't assign the array itself to anything). 3. Retrieve the string at index [x] (x being the random number generated on line 12) of the newly created String array. 4. Assign the string retrieved from the array to the declared instance variable name. We could have made it much easier to read if we'd just written String[] nameList = {"Fluffy", "Fido", "Rover", "Spike", "Gigi"}; String name = nameList[x]; But where's the fun in that? Throwing in unusual syntax (especially for code wholly unrelated to the real question) is in the spirit of the exam. Don't be 138 Chapter 2: Object Orientation startled! (OK, be startled, but then just say to yourself, "Whoa" and get on with it.) n Line 18 We're invoking the no-arg version of the constructor (causing a random name from the list to be passed to the other constructor). n Line 20 We're invoking the overloaded constructor that takes a string representing the name. The key point to get from this code example is in line 8. Rather than calling super(), we're calling this(), and this() always means a call to another constructor in the same class. OK, fine, but what happens after the call to this()? Sooner or later the super() constructor gets called, right? Yes indeed. A call to this() just means you're delaying the inevitable. Some constructor, somewhere, must make the call to super(). Key Rule: The first line in a constructor must be a call to super() or a call to this(). No exceptions. If you have neither of those calls in your constructor, the compiler will insert the no-arg call to super(). In other words, if constructor A() has a call to this(), the compiler knows that constructor A() will not be the one to invoke super(). The preceding rule means a constructor can never have both a call to super() and a call to this(). Because each of those calls must be the first statement in a constructor, you can't legally use both in the same constructor. That also means the compiler will not put a call to super() in any constructor that has a call to this(). Thought question: What do you think will happen if you try to compile the following code? class A { A() { this("foo"); } A(String s) { this(); } } Your compiler may not actually catch the problem (it varies depending on your compiler, but most won't catch the problem). It assumes you know what you're Overloaded Constructors (Exam Objectives 1.6 & 5.4) 139 doing. Can you spot the flaw? Given that a super constructor must always be called, where would the call to super() go? Remember, the compiler won't put in a default constructor if you've already got one or more constructors in your class. And when the compiler doesn't put in a default constructor, it still inserts a call to super() in any constructor that doesn't explicitly have a call to the super constructor—unless, that is, the constructor already has a call to this(). So in the preceding code, where can super() go? The only two constructors in the class both have calls to this(), and in fact you'll get exactly what you'd get if you typed the following method code: public void go() { doStuff(); } public void doStuff() { go(); } Now can you see the problem? Of course you can. The stack explodes! It gets higher and higher and higher until it just bursts open and method code goes spilling out, oozing out of the JVM right onto the floor. Two overloaded constructors both calling this() are two constructors calling each other. Over and over and over, resulting in % java A Exception in thread "main" java.lang.StackOverflowError The benefit of having overloaded constructors is that you offer flexible ways to instantiate objects from your class. The benefit of having one constructor invoke another overloaded constructor is to avoid code duplication. In the Animal example, there wasn't any code other than setting the name, but imagine if after line 4 there was still more work to be done in the constructor. By putting all the other constructor work in just one constructor, and then having the other constructors invoke it, you don't have to write and maintain multiple versions of that other important constructor code. Basically, each of the other not-the-real-one overloaded constructors will call another overloaded constructor, passing it whatever data it needs (data the client code didn't supply). Constructors and instantiation become even more exciting (just when you thought it was safe), when you get to inner classes, but we know you can stand to 140 Chapter 2: Object Orientation have only so much fun in one chapter, so we're holding the rest of the discussion on instantiating inner classes until Chapter 8. CERTIFICATION OBJECTIVE Statics (Exam Objective 1.3) 1.3 Develop code that declares, initializes, and uses primitives, arrays, enums, and objects as static, instance, and local variables. Also, use legal identifiers for variable names. Static Variables and Methods The static modifier has such a profound impact on the behavior of a method or variable that we're treating it as a concept entirely separate from the other modifiers. To understand the way a static member works, we'll look first at a reason for using one. Imagine you've got a utility class with a method that always runs the same way; its sole function is to return, say, a random number. It wouldn't matter which instance of the class performed the method—it would always behave exactly the same way. In other words, the method's behavior has no dependency on the state (instance variable values) of an object. So why, then, do you need an object when the method will never be instance-specific? Why not just ask the class itself to run the method? Let's imagine another scenario: Suppose you want to keep a running count of all instances instantiated from a particular class. Where do you actually keep that variable? It won't work to keep it as an instance variable within the class whose instances you're tracking, because the count will just be initialized back to a default value with each new instance. The answer to both the utility-method-always-runsthe- same scenario and the keep-a-running-total-of-instances scenario is to use the static modifier. Variables and methods marked static belong to the class, rather than to any particular instance. In fact, you can use a static method or variable without having any instances of that class at all. You need only have the class available to be able to invoke a static method or access a static variable. static variables, too, can be accessed without having an instance of a class. But if there are instances, a static variable of a class will be shared by all instances of that class; there is only one copy. The following code declares and uses a static counter variable: Static Variables and Methods (Exam Objective 1.3) 141 class Frog { static int frogCount = 0; // Declare and initialize // static variable public Frog() { frogCount += 1; // Modify the value in the constructor } public static void main (String [] args) { new Frog(); new Frog(); new Frog(); System.out.println("Frog count is now " + frogCount); } } In the preceding code, the static frogCount variable is set to zero when the Frog class is first loaded by the JVM, before any Frog instances are created! (By the way, you don't actually need to initialize a static variable to zero; static variables get the same default values instance variables get.) Whenever a Frog instance is created, the Frog constructor runs and increments the static frogCount variable. When this code executes, three Frog instances are created in main(), and the result is Frog count is now 3 Now imagine what would happen if frogCount were an instance variable (in other words, nonstatic): class Frog { int frogCount = 0; // Declare and initialize // instance variable public Frog() { frogCount += 1; // Modify the value in the constructor } public static void main (String [] args) { new Frog(); new Frog(); new Frog(); System.out.println("Frog count is now " + frogCount); } } When this code executes, it should still create three Frog instances in main(), but the result is...a compiler error! We can't get this code to compile, let alone run. 142 Chapter 2: Object Orientation Frog.java:11: non-static variable frogCount cannot be referenced from a static context System.out.println("Frog count is " + frogCount); ^ 1 error The JVM doesn't know which Frog object's frogCount you're trying to access. The problem is that main() is itself a static method, and thus isn't running against any particular instance of the class, rather just on the class itself. A static method can't access a nonstatic (instance) variable, because there is no instance! That's not to say there aren't instances of the class alive on the heap, but rather that even if there are, the static method doesn't know anything about them. The same applies to instance methods; a static method can't directly invoke a nonstatic method. Think static = class, nonstatic = instance. Making the method called by the JVM (main()) a static method means the JVM doesn't have to create an instance of your class just to start running code. Statics (Exam Objective 1.3) 143 One of the mistakes most often made by new Java programmers is attempting to access an instance variable (which means nonstatic variable) from the static main() method (which doesn’t know anything about any instances, so it can’t access the variable). The following code is an example of illegal access of a nonstatic variable from a static method: class Foo { int x = 3; public static void main (String [] args) { System.out.println("x is " + x); } } Understand that this code will never compile, because you can’t access a nonstatic (instance) variable from a static method. Just think of the compiler saying, “Hey, I have no idea which Foo object’s x variable you’re trying to print!” Remember, it’s the class running the main() method, not an instance of the class. Accessing Static Methods and Variables Since you don't need to have an instance in order to invoke a static method or access a static variable, then how do you invoke or use a static member? What's the syntax? We know that with a regular old instance method, you use the dot operator on a reference to an instance: class Frog { int frogSize = 0; public int getFrogSize() { return frogSize; } public Frog(int s) { frogSize = s; } public static void main (String [] args) { 144 Chapter 2: Object Orientation Continued... Of course, the tricky part for the exam is that the question won’t look as obvious as the preceding code. The problem you’re being tested for— accessing a nonstatic variable from a static method—will be buried in code that might appear to be testing something else. For example, the preceding code would be more likely to appear as class Foo { int x = 3; float y = 4.3f; public static void main (String [] args) { for (int z = x; z < ++x; z--, y = y + z) { // complicated looping and branching code } } So while you’re trying to follow the logic, the real issue is that x and y can’t be used within main(), because x and y are instance, not static, variables! The same applies for accessing nonstatic methods from a static method. The rule is, a static method of a class can’t access a nonstatic (instance) method or variable of its own class. Frog f = new Frog(25); System.out.println(f.getFrogSize()); // Access instance // method using f } } In the preceding code, we instantiate a Frog, assign it to the reference variable f, and then use that f reference to invoke a method on the Frog instance we just created. In other words, the getFrogSize() method is being invoked on a specific Frog object on the heap. But this approach (using a reference to an object) isn't appropriate for accessing a static method, because there might not be any instances of the class at all! So, the way we access a static method (or static variable) is to use the dot operator on the class name, as opposed to using it on a reference to an instance, as follows: class Frog { static int frogCount = 0; // Declare and initialize // static variable public Frog() { frogCount += 1; // Modify the value in the constructor } } class TestFrog { public static void main (String [] args) { new Frog(); new Frog(); new Frog(); System.out.print("frogCount:"+Frog.frogCount); //Access // static variable } } But just to make it really confusing, the Java language also allows you to use an object reference variable to access a static member: Frog f = new Frog(); int frogs = f.frogCount; // Access static variable // FrogCount using f Statics (Exam Objective 1.3) 145 In the preceding code, we instantiate a Frog, assign the new Frog object to the reference variable f, and then use the f reference to invoke a static method! But even though we are using a specific Frog instance to access the static method, the rules haven't changed. This is merely a syntax trick to let you use an object reference variable (but not the object it refers to) to get to a static method or variable, but the static member is still unaware of the particular instance used to invoke the static member. In the Frog example, the compiler knows that the reference variable f is of type Frog, and so the Frog class static method is run with no awareness or concern for the Frog instance at the other end of the f reference. In other words, the compiler cares only that reference variable f is declared as type Frog. Figure 2-8 illustrates the effects of the static modifier on methods and variables. FIGURE 2-8 The effects of static on methods and variables 146 Chapter 2: Object Orientation Finally, remember that static methods can't be overridden! This doesn't mean they can't be redefined in a subclass, but redefining and overriding aren't the same thing. Let's take a look at an example of a redefined (remember, not overridden), static method: class Animal { static void doStuff() { System.out.print("a "); } } class Dog extends Animal { static void dostuff() { // it's a redefinition, // not an override System.out.print("d "); } public static void main(String [] args) { Animal [] a = {new Animal(), new Dog(), new Animal()}; for(int x = 0; x < a.length; x++) a[x].doStuff(); // invoke the static method } } Running this code produces the output: a a a Remember, the syntax a[x].doStuff() is just a shortcut (the syntax trick)…the compiler is going to substitute something like Animal.doStuff() instead. Notice that we didn't use the Java 1.5 enhanced for loop here (covered in Chapter 5), even though we could have. Expect to see a mix of both Java 1.4 and Java 5 coding styles and practices on the exam. CERTIFICATION OBJECTIVE Coupling and Cohesion (Exam Objective 5.1) 5.1 Develop code that implements tight encapsulation, loose coupling, and high cohesion in classes, and describe the benefits. Statics (Exam Objective 1.3) 147 We're going to admit it up front. The Sun exam's definitions for cohesion and coupling are somewhat subjective, so what we discuss in this chapter is from the perspective of the exam, and by no means The One True Word on these two OO design principles. It may not be exactly the way that you've learned it, but it's what you need to understand to answer the questions. You'll have very few questions about coupling and cohesion on the real exam. These two topics, coupling and cohesion, have to do with the quality of an OO design. In general, good OO design calls for loose coupling and shuns tight coupling, and good OO design calls for high cohesion, and shuns low cohesion. As with most OO design discussions, the goals for an application are n Ease of creation n Ease of maintenance n Ease of enhancement Coupling Let's start by making an attempt at a definition of coupling. Coupling is the degree to which one class knows about another class. If the only knowledge that class A has about class B, is what class B has exposed through its interface, then class A and class B are said to be loosely coupled…that's a good thing. If, on the other hand, class A relies on parts of class B that are not part of class B's interface, then the coupling between the classes is tighter…not a good thing. In other words, if A knows more than it should about the way in which B was implemented, then A and B are tightly coupled. Using this second scenario, imagine what happens when class B is enhanced. It's quite possible that the developer enhancing class B has no knowledge of class A, why would she? Class B's developer ought to feel that any enhancements that don't break the class's interface should be safe, so she might change some non-interface part of the class, which then causes class A to break. At the far end of the coupling spectrum is the horrible situation in which class A knows non-API stuff about class B, and class B knows non-API stuff about class A… this is REALLY BAD. If either class is ever changed, there's a chance that the other class will break. Let's look at an obvious example of tight coupling, which has been enabled by poor encapsulation: class DoTaxes { float rate; 148 Chapter 2: Object Orientation float doColorado() { SalesTaxRates str = new SalesTaxRates(); rate = str.salesRate; // ouch // this should be a method call: // rate = str.getSalesRate("CO"); // do stuff with rate } } class SalesTaxRates { public float salesRate; // should be private public float adjustedSalesRate; // should be private public float getSalesRate(String region) { salesRate = new DoTaxes().doColorado(); // ouch again! // do region-based calculations return adjustedSalesRate; } } All non-trivial OO applications are a mix of many classes and interfaces working together. Ideally, all interactions between objects in an OO system should use the APIs, in other words, the contracts, of the objects' respective classes. Theoretically, if all of the classes in an application have well-designed APIs, then it should be possible for all interclass interactions to use those APIs exclusively. As we discussed earlier in this chapter, an aspect of good class and API design is that classes should be well encapsulated. The bottom line is that coupling is a somewhat subjective concept. Because of this, the exam will test you on really obvious examples of tight coupling; you won't be asked to make subtle judgment calls. Cohesion While coupling has to do with how classes interact with each other, cohesion is all about how a single class is designed. The term cohesion is used to indicate the degree to which a class has a single, well-focused purpose. Keep in mind that cohesion is a subjective concept. The more focused a class is, the higher its cohesiveness—a good thing. The key benefit of high cohesion is that such classes are typically much easier to maintain (and less frequently changed) than classes with low cohesion. Another benefit of high cohesion is that classes with a well-focused purpose tend to be more reusable than other classes. Let's take a look at a pseudo-code example: Coupling and Cohesion (Exam Objective 5.1) 149 class BudgetReport { void connectToRDBMS(){ } void generateBudgetReport() { } void saveToFile() { } void print() { } } Now imagine your manager comes along and says, "Hey you know that accounting application we're working on? The clients just decided that they're also going to want to generate a revenue projection report, oh and they want to do some inventory reporting also. They do like our reporting features however, so make sure that all of these reports will let them choose a database, choose a printer, and save generated reports to data files..." Ouch! Rather than putting all the printing code into one report class, we probably would have been better off with the following design right from the start: class BudgetReport { Options getReportingOptions() { } void generateBudgetReport(Options o) { } } class ConnectToRDBMS { DBconnection getRDBMS() { } } class PrintStuff { PrintOptions getPrintOptions() { } } class FileSaver { SaveOptions getFileSaveOptions() { } } This design is much more cohesive. Instead of one class that does everything, we've broken the system into four main classes, each with a very specific, or cohesive, role. Because we've built these specialized, reusable classes, it'll be much easier to write a new report, since we've already got the database connection class, the printing class, and the file saver class, and that means they can be reused by other classes that might want to print a report. 150 Chapter 2: Object Orientation CERTIFICATION SUMMARY We started the chapter by discussing the importance of encapsulation in good OO design, and then we talked about how good encapsulation is implemented: with private instance variables and public getters and setters. Next, we covered the importance of inheritance; so that you can grasp overriding, overloading, polymorphism, reference casting, return types, and constructors. We covered IS-A and HAS-A. IS-A is implemented using inheritance, and HAS-A is implemented by using instance variables that refer to other objects. Polymorphism was next. Although a reference variable's type can't be changed, it can be used to refer to an object whose type is a subtype of its own. We learned how to determine what methods are invocable for a given reference variable. We looked at the difference between overridden and overloaded methods, learning that an overridden method occurs when a subclass inherits a method from a superclass, and then re-implements the method to add more specialized behavior. We learned that, at runtime, the JVM will invoke the subclass version on an instance of a subclass, and the superclass version on an instance of the superclass. Abstract methods must be "overridden" (technically, abstract methods must be implemented, as opposed to overridden, since there really isn't anything to override. We saw that overriding methods must declare the same argument list and return type (or, as of Java 5, they can return a subtype of the declared return type of the superclass overidden method), and that the access modifier can't be more restrictive. The overriding method also can't throw any new or broader checked exceptions that weren't declared in the overridden method. You also learned that the overridden method can be invoked using the syntax super.doSomething();. Overloaded methods let you reuse the same method name in a class, but with different arguments (and, optionally, a different return type). Whereas overriding methods must not change the argument list, overloaded methods must. But unlike overriding methods, overloaded methods are free to vary the return type, access modifier, and declared exceptions any way they like. We learned the mechanics of casting (mostly downcasting), reference variables, when it's necessary, and how to use the instanceof operator. Implementing interfaces came next. An interface describes a contract that the implementing class must follow. The rules for implementing an interface are similar to those for extending an abstract class. Also remember that a class can implement more than one interface, and that interfaces can extend another interface. We also looked at method return types, and saw that you can declare any return type you like (assuming you have access to a class for an object reference return Certification Summary 151 type), unless you're overriding a method. Barring a covariant return, an overriding method must have the same return type as the overridden method of the superclass. We saw that while overriding methods must not change the return type, overloaded methods can (as long as they also change the argument list). Finally, you learned that it is legal to return any value or variable that can be implicitly converted to the declared return type. So, for example, a short can be returned when the return type is declared as an int. And (assuming Horse extends Animal), a Horse reference can be returned when the return type is declared an Animal. We covered constructors in detail, learning that if you don't provide a constructor for your class, the compiler will insert one. The compiler-generated constructor is called the default constructor, and it is always a no-arg constructor with a no-arg call to super(). The default constructor will never be generated if there is even a single constructor in your class (regardless of the arguments of that constructor), so if you need more than one constructor in your class and you want a no-arg constructor, you'll have to write it yourself. We also saw that constructors are not inherited, and that you can be confused by a method that has the same name as the class (which is legal). The return type is the giveaway that a method is not a constructor, since constructors do not have return types. We saw how all of the constructors in an object's inheritance tree will always be invoked when the object is instantiated using new. We also saw that constructors can be overloaded, which means defining constructors with different argument lists. A constructor can invoke another constructor of the same class using the keyword this(), as though the constructor were a method named this(). We saw that every constructor must have either this() or super() as the first statement. We looked at static methods and variables. Static members are tied to the class, not an instance, so there is only one copy of any static member. A common mistake is to attempt to reference an instance variable from a static method. Use the class name with the dot operator to access static members. We discussed the OO concepts of coupling and cohesion. Loose coupling is the desirable state of two or more classes that interact with each other only through their respective API's. Tight coupling is the undesirable state of two or more classes that know inside details about another class, details not revealed in the class's API. High cohesion is the desirable state of a single class whose purpose and responsibilities are limited and well-focused. And once again, you learned that the exam includes tricky questions designed largely to test your ability to recognize just how tricky the questions can be. 152 Chapter 2: Object Orientation TWO-MINUTE DRILL Here are some of the key points from each certification objective in this chapter. Encapsulation, IS-A, HAS-A (Objective 5.1) q Encapsulation helps hide implementation behind an interface (or API). q Encapsulated code has two features: q Instance variables are kept protected (usually with the private modifier). q Getter and setter methods provide access to instance variables. q IS-A refers to inheritance. q IS-A is expressed with the keyword extends. q IS-A, "inherits from," and "is a subtype of" are all equivalent expressions. q HAS-A means an instance of one class "has a" reference to an instance of another class. Inheritance (Objective 5.5) q Inheritance is a mechanism that allows a class to be a subclass of a superclass, and thereby inherit variables and methods of the superclass. q Inheritance is a key concept that underlies IS-A, polymorphism, overriding, overloading, and casting. q All classes (except class Object), are subclasses of type Object, and therefore they inherit Object's methods. Polymorphism (Objective 5.2) q Polymorphism means ‘many forms'. q A reference variable is always of a single, unchangeable type, but it can refer to a subtype object. q A single object can be referred to by reference variables of many different types— as long as they are the same type or a supertype of the object. q The reference variable's type (not the object's type), determines which methods can be called! q Polymorphic method invocations apply only to overridden instance methods. Two-Minute Drill 153 3 Overriding and Overloading (Objectives 1.5 and 5.4) q Methods can be overridden or overloaded; constructors can be overloaded but not overridden. q Abstract methods must be overridden by the first concrete (nonabstract) subclass. q With respect to the method it overrides, the overriding method q Must have the same argument list q Must have the same return type, except that as of Java 5, the return type can be a subclass—this is known as a covariant return. q Must not have a more restrictive access modifier q May have a less restrictive access modifier q Must not throw new or broader checked exceptions q May throw fewer or narrower checked exceptions, or any unchecked exception. q final methods cannot be overridden. q Only inherited methods may be overridden, and remember that private methods are not inherited. q A subclass uses super.overriddenMethodName() to call the superclass version of an overridden method. q Overloading means reusing amethod name, but with different arguments. q Overloaded methods q Must have different argument lists q May have different return types, if argument lists are also different q May have different access modifiers q May throw different exceptions q Methods from a superclass can be overloaded in a subclass. q Polymorphism applies to overriding, not to overloading q Object type (not the reference variable's type), determines which overridden method is used at runtime. q Reference type determines which overloaded method will be used at compile time. 154 Chapter 2: Object Orientation Reference Variable Casting (Objective 5.2) q There are two types of reference variable casting: downcasting and upcasting. q Downcasting: If you have a reference variable that refers to a subtype object, you can assign it to a reference variable of the subtype. You must make an explicit cast to do this, and the result is that you can access the subtype's members with this new reference variable. q Upcasting: You can assign a reference variable to a supertype reference variable explicitly or implicitly. This is an inherently safe operation because the assignment restricts the access capabilities of the new variable. Implementing an Interface (Objective 1.2) q When you implement an interface, you are fulfilling its contract. q You implement an interface by properly and concretely overriding all of the methods defined by the interface. q A single class can implement many interfaces. Return Types (Objective 1.5) q Overloaded methods can change return types; overridden methods cannot, except in the case of covariant returns. q Object reference return types can accept null as a return value. q An array is a legal return type, both to declare and return as a value. q For methods with primitive return types, any value that can be implicitly converted to the return type can be returned. q Nothing can be returned from a void, but you can return nothing. You're allowed to simply say return, in any method with a void return type, to bust out of a method early. But you can't return nothing from a method with a non-void return type. q Methods with an object reference return type, can return a subtype. q Methods with an interface return type, can return any implementer. Constructors and Instantiation (Objectives 1.6 and 5.4) q You cannot create a new object without invoking a constructor. Two-Minute Drill 155 q Each superclass in an object's inheritance tree will have a constructor called. q Every class, even an abstract class, has at least one constructor. q Constructors must have the same name as the class. q Constructors don't have a return type. If the code you're looking at has a return type, it's a method with the same name as the class, and a constructor. q Typical constructor execution occurs as follows: q The constructor calls its superclass constructor, which calls its superclass constructor, and so on all the way up to the Object constructor. q The Object constructor executes and then returns to the calling constructor, which runs to completion and then returns to its calling constructor, and so on back down to the completion of the constructor of the actual instance being created. q Constructors can use any access modifier (even private!). q The compiler will create a default constructor if you don't create any constructors in your class. q The default constructor is a no-arg constructor with a no-arg call to super(). q The first statement of every constructor must be a call to either this(), (an overloaded constructor), or super(). q The compiler will add a call to super() if you do not, unless you have already put in a call to this(). q Instance members are accessible only after the super constructor runs. q Abstract classes have constructors that are called when a concrete subclass is instantiated. q Interfaces do not have constructors. q If your superclass does not have a no-arg constructor, you must create a constructor and insert a call to super() with arguments matching those of the superclass constructor. q Constructors are never inherited, thus they cannot be overridden. q A constructor can be directly invoked only by another constructor (using a call to super() or this()). q Issues with calls to this(): q May appear only as the first statement in a constructor. q The argument list determines which overloaded constructor is called. 156 Chapter 2: Object Orientation q Constructors can call constructors can call constructors, and so on, but sooner or later one of them better call super() or the stack will explode. q Calls to this() and super() cannot be in the same constructor. You can have one or the other, but never both. Statics (Objective 1.3) q Use static methods to implement behaviors that are not affected by the state of any instances. q Use static variables to hold data that is class specific as opposed to instance specific—there will be only one copy of a static variable. q All static members belong to the class, not to any instance. q A static method can't access an instance variable directly. q Use the dot operator to access static members, but remember that using a reference variable with the dot operator is really a syntax trick, and the compiler will substitute the class name for the reference variable, for instance: d.doStuff(); becomes: Dog.doStuff(); q static methods can't be overridden, but they can be redefined. Coupling and Cohesion (Objective 5.1) q Coupling refers to the degree to which one class knows about or uses members of another class. q Loose coupling is the desirable state of having classes that are well encapsulated, minimize references to each other, and limit the breadth of API usage. q Tight coupling is the undesirable state of having classes that break the rules of loose coupling. q Cohesion refers to the degree in which a class has a single, well-defined role or responsibility. q High cohesion is the desirable state of a class whose members support a single, well-focused role or responsibility. q Low cohesion is the undesirable state of a class whose members support multiple, unfocused roles or responsibilities. Two-Minute Drill 157 Self Test 1. Which statement(s) are true? (Choose all that apply.) A. Has-a relationships always rely on inheritance. B. Has-a relationships always rely on instance variables. C. Has-a relationships always require at least two class types. D. Has-a relationships always rely on polymorphism. E. Has-a relationships are always tightly coupled. 2. Given: class Clidders { public final void flipper() { System.out.println("Clidder"); } } public class Clidlets extends Clidders { public void flipper() { System.out.println("Flip a Clidlet"); super.flipper(); } public static void main(String [] args) { new Clidlets().flipper(); } } What is the result? A. Flip a Clidlet B. Flip a Clidder C. Flip a Clidder Flip a Clidlet D. Flip a Clidlet Flip a Clidder E. Compilation fails. 3. Given: public abstract interface Frobnicate { public void twiddle(String s); } Which is a correct class? (Choose all that apply.) A. public abstract class Frob implements Frobnicate { public abstract void twiddle(String s) { } } 158 Chapter 2: Object Orientation B. public abstract class Frob implements Frobnicate { } C. public class Frob extends Frobnicate { public void twiddle(Integer i) { } } D. public class Frob implements Frobnicate { public void twiddle(Integer i) { } } E. public class Frob implements Frobnicate { public void twiddle(String i) { } public void twiddle(Integer s) { } } 4. Given: class Top { public Top(String s) { System.out.print("B"); } } public class Bottom2 extends Top { public Bottom2(String s) { System.out.print("D"); } public static void main(String [] args) { new Bottom2("C"); System.out.println(" "); } } What is the result? A. BD B. DB C. BDC D. DBC E. Compilation fails. 5. Select the two statements that best indicate a situation with low coupling. (Choose two.) A. The attributes of the class are all private. B. The class refers to a small number of other objects. C. The object contains only a small number of variables. D. The object is referred to using an anonymous variable, not directly. E. The reference variable is declared for an interface type, not a class. The interface provides a small number of methods. F. It is unlikely that changes made to one class will require any changes in another. Self Test 159 160 Chapter 2: Object Orientation 6. Given: class Clidder { private final void flipper() { System.out.println("Clidder"); } } public class Clidlet extends Clidder { public final void flipper() { System.out.println("Clidlet"); } public static void main(String [] args) { new Clidlet().flipper(); } } What is the result? A. Clidlet B. Clidder C. Clidder Clidlet D. Clidlet Clidder E. Compilation fails. 7. Using the fragments below, complete the following code so it compiles. Note, you may not have to fill all of the slots. Code: class AgedP { __________ __________ __________ __________ __________ public AgedP(int x) { __________ __________ __________ __________ __________ } } public class Kinder extends AgedP { __________ __________ __________ _________ ________ __________ public Kinder(int x) { __________ __________ __________ __________ __________ (); } } Fragments: Use the following fragments zero or more times: AgedP super this ( ) { }

8. Given: 1. class Plant { 2. String getName() { return "plant"; } 3. Plant getType() { return this; } 4. } 5. class Flower extends Plant { 6. // insert code here 7. } 8. class Tulip extends Flower { } Which statement(s), inserted at line 6, will compile? (Choose all that apply.) A. Flower getType() { return this; } B. String getType() { return "this"; } C. Plant getType() { return this; } D. Tulip getType() { return new Tulip(); } 9. Given: 1. class Zing { 2. protected Hmpf h; 3. } 4. class Woop extends Zing { } 5. class Hmpf { } Which is true? (Choose all that apply.) A. Woop is-a Hmpf and has-a Zing. B. Zing is-a Woop and has-a Hmpf. C. Hmpf has-a Woop and Woop is-a Zing. D. Woop has-a Hmpf and Woop is-a Zing. E. Zing has-a Hmpf and Zing is-a Woop. Self Test 161 162 Chapter 2: Object Orientation 10. Given: 1. class Programmer { 2. Programmer debug() { return this; } 3. } 4. class SCJP extends Programmer { 5. // insert code here 6. } Which, inserted at line 5, will compile? (Choose all that apply.) A. Programmer debug() { return this; } B. SCJP debug() { return this; } C. Object debug() { return this; } D. int debug() { return 1; } E. int debug(int x) { return 1; } F. Object debug(int x) { return this; } 11. Given: class Uber { static int y = 2; Uber(int x) { this(); y = y * 2; } Uber() { y++; } } class Minor extends Uber { Minor() { super(y); y = y + 3; } public static void main(String [] args) { new Minor(); System.out.println(y); } } What is the result? A. 6 B. 7 C. 8 D. 9 E. Compilation fails. F. An exception is thrown. 12. Which statement(s) are true? (Choose all that apply.) A. Cohesion is the OO principle most closely associated with hiding implementation details. B. Cohesion is the OO principle most closely associated with making sure that classes know about other classes only through their APIs. C. Cohesion is the OO principle most closely associated with making sure that a class is designed with a single, well-focused purpose. D. Cohesion is the OO principle most closely associated with allowing a single object to be seen as having many types. 13. Given: 1. class Dog { } 2. class Beagle extends Dog { } 3. 4. class Kennel { 5. public static void main(String [] arfs) { 6. Beagle b1 = new Beagle(); 7. Dog dog1 = new Dog(); 8. Dog dog2 = b1; 9. // insert code here 10. } } Which, inserted at line 9, will compile? (Choose all that apply.) A. Beagle b2 = (Beagle) dog1; B. Beagle b3 = (Beagle) dog2; C. Beagle b4 = dog2; D. None of the above statements will compile. 14. Given the following, 1. class X { void do1() { } } 2. class Y extends X { void do2() { } } 3. 4. class Chrome { 5. public static void main(String [] args) { 6. X x1 = new X(); 7. X x2 = new Y(); 8. Y y1 = new Y(); 9. // insert code here 10. } } Which, inserted at line 9, will compile? (Choose all that apply.) A. x2.do2(); B. (Y)x2.do2(); C. ((Y)x2).do2(); D. None of the above statements will compile. Self Test 163 SElf Test Answers 1. Which statement(s) are true? (Choose all that apply.) A. Has-a relationships always rely on inheritance. B. Has-a relationships always rely on instance variables. C. Has-a relationships always require at least two class types. D. Has-a relationships always rely on polymorphism. E. Has-a relationships are always tightly coupled. Answer: ® 3 B is correct. ®˚ A and D describe other OO topics. C is incorrect because a class can have an instance of itself. E is incorrect because while has-a relationships can lead to tight coupling, it is by no means always the case. (Objective 5.5) 2. Given: class Clidders { public final void flipper() { System.out.println("Clidder"); } } public class Clidlets extends Clidders { public void flipper() { System.out.println("Flip a Clidlet"); super.flipper(); } public static void main(String [] args) { new Clidlets().flipper(); } } What is the result? A. Flip a Clidlet B. Flip a Clidder C. Flip a Clidder Flip a Clidlet D. Flip a Clidlet Flip a Clidder E. Compilation fails. 164 Chapter 2: Object Orientation Answer: ® 3 E is correct. final methods cannot be overridden. ®˚ A, B, C, and D are incorrect based on the above. (Objective 5.3) 3. Given: public abstract interface Frobnicate { public void twiddle(String s); } Which is a correct class? (Choose all that apply.) A. public abstract class Frob implements Frobnicate { public abstract void twiddle(String s) { } } B. public abstract class Frob implements Frobnicate { } C. public class Frob extends Frobnicate { public void twiddle(Integer i) { } } D. public class Frob implements Frobnicate { public void twiddle(Integer i) { } } E. public class Frob implements Frobnicate { public void twiddle(String i) { } public void twiddle(Integer s) { } } Answer: ® 3 B is correct, an abstract class need not implement any or all of an interface’s methods. E is correct, the class implements the interface method and additionally overloads the twiddle() method. ®˚ A is incorrect because abstract methods have no body. C is incorrect because classes implement interfaces they don’t extend them. D is incorrect because overloading a method is not implementing it. (Objective 5.4) 4. Given: class Top { public Top(String s) { System.out.print("B"); } } public class Bottom2 extends Top { public Bottom2(String s) { System.out.print("D"); } public static void main(String [] args) { Self Test Answers 165 new Bottom2("C"); System.out.println(" "); } } What is the result? A. BD B. DB C. BDC D. DBC E. Compilation fails. Answer: ® 3 E is correct. The implied super() call in Bottom2’s constructor cannot be satisfied because there isn’t a no-arg constructor in Top. A default, no-arg constructor is generated by the compiler only if the class has no constructor defined explicitly. ®˚ A, B, C, and D are incorrect based on the above. (Objective 1.6) 5. Select the two statements that best indicate a situation with low coupling. (Choose two.) A. The attributes of the class are all private. B. The class refers to a small number of other objects. C. The object contains only a small number of variables. D. The object is referred to using an anonymous variable, not directly. E. The reference variable is declared for an interface type, not a class. The interface provides a small number of methods. F. It is unlikely that changes made to one class will require any changes in another. Answer: ® 3 E and F are correct. Only having access to a small number of methods implies limited coupling. If the access is via a reference of interface type, it may be argued that there is even less opportunity for coupling as the class type itself is not visible. Stating that changes in one part of a program are unlikely to cause consequences in another part is really the essence of low coupling. There is no such thing as an anonymous variable. Referring to only a small number of other objects might imply low coupling, but if each object has many methods, and all are used, then coupling is high. Variables (attributes) in a class should usually be private, but this describes encapsulation, rather than low coupling. Of course, good encapsulation tends to reduce coupling as a consequence. ®˚ A, B, C and D are incorrect based on the preceding treatise. (Objective 5.1) 166 Chapter 2: Object Orientation 6. Given: class Clidder { private final void flipper() { System.out.println("Clidder"); } } public class Clidlet extends Clidder { public final void flipper() { System.out.println("Clidlet"); } public static void main(String [] args) { new Clidlet().flipper(); } } What is the result? A. Clidlet B. Clidder C. Clidder Clidlet D. Clidlet Clidder E. Compilation fails. Answer: ® 3 A is correct. Although a final method cannot be overridden, in this case, the method is private, and therefore hidden. The effect is that a new, accessible, method flipper is created. Therefore, no polymorphism occurs in this example, the method invoked is simply that of the child class, and no error occurs. ®˚ B, C, D, and E are incorrect based on the preceding. (Objective 5.3) 7. Using the fragments below, complete the following code so it compiles. Note, you may not have to fill all of the slots. Code: class AgedP { __________ __________ __________ __________ __________ public AgedP(int x) { __________ __________ __________ __________ __________ } } public class Kinder extends AgedP { __________ __________ __________ _________ ________ __________ Self Test Answers 167 168 Chapter 2: Object Orientation public Kinder(int x) { __________ __________ __________ __________ __________ (); } } Fragments: Use the following fragments zero or more times: AgedP super this ( ) { }

Answer: class AgedP { AgedP() {} public AgedP(int x) { } } public class Kinder extends AgedP { public Kinder(int x) { super(); } } As there is no droppable tile for the variable x and the parentheses (in the Kinder constructor), are already in place and empty, there is no way to construct a call to the superclass constructor that takes an argument. Therefore, the only remaining possibility is to create a call to the no-argument superclass constructor. This is done as: super();. The line cannot be left blank, as the parentheses are already in place. Further, since the superclass constructor called is the no-argument version, this constructor must be created. It will not be created by the compiler because there is another constructor already present. (Objective 5.4) 8. Given: 1. class Plant { 2. String getName() { return "plant"; } 3. Plant getType() { return this; } 4. } 5. class Flower extends Plant { 6. // insert code here 7. } 8. class Tulip extends Flower { } Which statement(s), inserted at line 6, will compile? (Choose all that apply.) A. Flower getType() { return this; } B. String getType() { return "this"; } C. Plant getType() { return this; } D. Tulip getType() { return new Tulip(); } Answer: ® 3 A, C, and D are correct. A and D are examples of co-variant returns, i.e., Flower and Tulip are both subtypes of Plant. ®˚ B is incorrect, String is not a subtype of Plant. (Objective 1.5) 9. Given: 1. class Zing { 2. protected Hmpf h; 3. } 4. class Woop extends Zing { } 5. class Hmpf { } Which is true? (Choose all that apply.) A. Woop is-a Hmpf and has-a Zing. B. Zing is-a Woop and has-a Hmpf. C. Hmpf has-a Woop and Woop is-a Zing. D. Woop has-a Hmpf and Woop is-a Zing. E. Zing has-a Hmpf and Zing is-a Woop. Answer: ® 3 D is correct, Woop inherits a Hmpf from Zing. ®˚ A, B, C, and E are incorrect based on the preceding. (Objective 5.5) 10. Given: 1. class Programmer { 2. Programmer debug() { return this; } 3. } 4. class SCJP extends Programmer { 5. // insert code here 6. } Self Test Answers 169 170 Chapter 2: Object Orientation Which, inserted at line 5, will compile? (Choose all that apply.) A. Programmer debug() { return this; } B. SCJP debug() { return this; } C. Object debug() { return this; } D. int debug() { return 1; } E. int debug(int x) { return 1; } F. Object debug(int x) { return this; } Answer: ® 3 A, B, E, and F are correct. A and B are examples of overriding, specifically, B is an example of overriding using a covariant return. E and F are examples of overloading. ®˚ C and D are incorrect. They are illegal overrides because their return types are incompatible. They are illegal overloads because their arguments did not change. (Objective 5.4) 11. Given: class Uber { static int y = 2; Uber(int x) { this(); y = y * 2; } Uber() { y++; } } class Minor extends Uber { Minor() { super(y); y = y + 3; } public static void main(String [] args) { new Minor(); System.out.println(y); } } What is the result? A. 6 B. 7 C. 8 D. 9 E. Compilation fails. F. An exception is thrown. Answer: ® 3 D is correct. Minor’s constructor makes an explicit call to Uber’s 1-arg constructor, which makes an explicit (this) call to Uber’s no-arg constructor, which increments y, then returns to the 1-arg constructor, which multiples y * 2, and then returns to Minor’s constructor, which adds 3 to y. ®˚ A, B, C, E, and F are incorrect based on the preceding. (Objective 1.6) 12. Which statement(s) are true? (Choose all that apply.) A. Cohesion is the OO principle most closely associated with hiding implementation details. B. Cohesion is the OO principle most closely associated with making sure that classes know about other classes only through their APIs. C. Cohesion is the OO principle most closely associated with making sure that a class is designed with a single, well-focused purpose. D. Cohesion is the OO principle most closely associated with allowing a single object to be seen as having many types. Answer: ® 3 Answer C is correct. ®˚ A refers to encapsulation, B refers to coupling, and D refers to polymorphism. (Objective 5.1) 13. Given: 1. class Dog { } 2. class Beagle extends Dog { } 3. 4. class Kennel { 5. public static void main(String [] arfs) { 6. Beagle b1 = new Beagle(); 7. Dog dog1 = new Dog(); 8. Dog dog2 = b1; 9. // insert code here 10. } 11. } Which, inserted at line 9, will compile? (Choose all that apply.) Self Test Answers 171 172 Chapter 2: Object Orientation A. Beagle b2 = (Beagle) dog1; B. Beagle b3 = (Beagle) dog2; C. Beagle b4 = dog2; D. None of the above statements will compile Answer: ® 3 A and B are correct. However, at runtime, A will throw a ClassCastException because dog1 refers to a Dog object, which can’t necessarily do Beagle stuff. ®˚ C and D are incorrect based on the preceding. (Objective 5.2). 14. Given the following, 1. class X { void do1() { } } 2. class Y extends X { void do2() { } } 3. 4. class Chrome { 5. public static void main(String [] args) { 6. X x1 = new X(); 7. X x2 = new Y(); 8. Y y1 = new Y(); 9. // insert code here 10. } 11. } Which, inserted at line 9, will compile? (Choose all that apply.) A. x2.do2(); B. (Y)x2.do2(); C. ((Y)x2).do2(); D. None of the above statements will compile. Answer: ® 3 C is correct. Before you can invoke Y’s do2 method you have to cast x2 to be of type Y. Statement B looks like a proper cast but without the second set of parentheses, the compiler thinks it’s an incomplete statement. ®˚ A, B and D are incorrect based on the preceding. (Objective 5.2)

















3 Assignments CERTIFICATION OBJECTIVES l Use Class Members l Develop Wrapper Code & Autoboxing Code l Determine the Effects of Passing Variables into Methods l Recognize when Objects Become Eligible for Garbage Collection 3 Two-Minute Drill Q&A Self Test 174 Chapter 3: Assignments Stack and Heap—Quick Review For most people, understanding the basics of the stack and the heap makes it far easier to understand topics like argument passing, polymorphism, threads, exceptions, and garbage collection. In this section, we'll stick to an overview, but we'll expand these topics several more times throughout the book. For the most part, the various pieces (methods, variables, and objects) of Java programs live in one of two places in memory: the stack or the heap. For now, we're going to worry about only three types of things: instance variables, local variables, and objects: n Instance variables and objects live on the heap. n Local variables live on the stack. Let's take a look at a Java program, and how its various pieces are created and map into the stack and the heap: 1. class Collar { } 2. 3. class Dog { 4. Collar c; // instance variable 5. String name; // instance variable 6. 7. public static void main(String [] args) { 8. 9. Dog d; // local variable: d 10. d = new Dog(); 11. d.go(d); 12. } 13. void go(Dog dog) { // local variable: dog 14. c = new Collar(); 15. dog.setName("Fido"); 16. } 17. void setName(String dogName) { // local var: dogName 18. name = dogName; 19. // do more stuff 20. } 21. } Figure 3-1 shows the state of the stack and the heap once the program reaches line 19. Following are some key points: Stack and Heap—Quick Review 175 n Line 7—main() is placed on the stack. n Line 9—reference variable d is created on the stack, but there's no Dog object yet. n Line 10—a new Dog object is created and is assigned to the d reference variable. n Line 11—a copy of the reference variable d is passed to the go() method. n Line 13—the go() method is placed on the stack, with the dog parameter as a local variable. n Line 14—a new Collar object is created on the heap, and assigned to Dog's instance variable. n Line 17—setName() is added to the stack, with the dogName parameter as its local variable. n Line 18—the name instance variable now also refers to the String object. n Notice that two different local variables refer to the same Dog object. n Notice that one local variable and one instance variable both refer to the same String Aiko. n After Line 19 completes, setName() completes and is removed from the stack. At this point the local variable dogName disappears too, although the String object it referred to is still on the heap. Instance variables: - name - c Collar object Dog object String object "Aiko" The Heap setName() dogName go() dog main() d method local variables The Stack Figure 3-1 Overview of the Stack and the Heap 176 Chapter 3: Assignments CERTIFICATION OBJECTIVE Literals, Assignments, and Variables (Exam Objectives 1.3 and 7.6) 1.3 Develop code that declares, initializes, and uses primitives, arrays, enums, and objects as static, instance, and local variables. Also, use legal identifiers for variable names. 7.6 Write code that correctly applies the appropriate operators including assignment operators (limited to: =, +=, -=)... Literal Values for All Primitive Types A primitive literal is merely a source code representation of the primitive data types—in other words, an integer, floating-point number, boolean, or character that you type in while writing code. The following are examples of primitive literals: 'b' // char literal 42 // int literal false // boolean literal 2546789.343 // double literal Integer Literals There are three ways to represent integer numbers in the Java language: decimal (base 10), octal (base 8), and hexadecimal (base 16). Most exam questions with integer literals use decimal representations, but the few that use octal or hexadecimal are worth studying for. Even though the odds that you'll ever actually use octal in the real world are astronomically tiny, they were included in the exam just for fun. Decimal Literals Decimal integers need no explanation; you've been using them since grade one or earlier. Chances are you don't keep your checkbook in hex. (If you do, there's a Geeks Anonymous (GA) group ready to help.) In the Java language, they are represented as is, with no prefix of any kind, as follows: int length = 343; Octal Literals Octal integers use only the digits 0 to 7. In Java, you represent an integer in octal form by placing a zero in front of the number, as follows: class Octal { public static void main(String [] args) { int six = 06; // Equal to decimal 6 int seven = 07; // Equal to decimal 7 int eight = 010; // Equal to decimal 8 int nine = 011; // Equal to decimal 9 System.out.println("Octal 010 = " + eight); } } Notice that when we get past seven and are out of digits to use (we are allowed only the digits 0 through 7 for octal numbers), we revert back to zero, and one is added to the beginning of the number. You can have up to 21 digits in an octal number, not including the leading zero. If we run the preceding program, it displays the following: Octal 010 = 8 Hexadecimal Literals Hexadecimal (hex for short) numbers are constructed using 16 distinct symbols. Because we never invented single digit symbols for the numbers 10 through 15, we use alphabetic characters to represent these digits. Counting from 0 through 15 in hex looks like this: 0 1 2 3 4 5 6 7 8 9 a b c d e f Java will accept capital or lowercase letters for the extra digits (one of the few places Java is not case-sensitive!). You are allowed up to 16 digits in a hexadecimal number, not including the prefix 0x or the optional suffix extension L, which will be explained later. All of the following hexadecimal assignments are legal: class HexTest { public static void main (String [] args) { int x = 0X0001; int y = 0x7fffffff; int z = 0xDeadCafe; System.out.println("x = " + x + " y = " + y + " z = " + z); } } Literal Values for All Primitive Types (Exam Objectives 1.3 and 7.6) 177 178 Chapter 3: Assignments Running HexTest produces the following output: x = 1 y = 2147483647 z = -559035650 Don't be misled by changes in case for a hexadecimal digit or the 'x' preceding it. 0XCAFE and 0xcafe are both legal and have the same value. All three integer literals (octal, decimal, and hexadecimal) are defined as int by default, but they may also be specified as long by placing a suffix of L or l after the number: long jo = 110599L; long so = 0xFFFFl; // Note the lowercase 'l' Floating-Point Literals Floating-point numbers are defined as a number, a decimal symbol, and more numbers representing the fraction. double d = 11301874.9881024; In the preceding example, the number 11301874.9881024 is the literal value. Floating-point literals are defined as double (64 bits) by default, so if you want to assign a floating-point literal to a variable of type float (32 bits), you must attach the suffix F or f to the number. If you don't, the compiler will complain about a possible loss of precision, because you're trying to fit a number into a (potentially) less precise "container." The F suffix gives you a way to tell the compiler, "Hey, I know what I'm doing, and I'll take the risk, thank you very much." float f = 23.467890; // Compiler error, possible loss // of precision float g = 49837849.029847F; // OK; has the suffix "F" You may also optionally attach a D or d to double literals, but it is not necessary because this is the default behavior. double d = 110599.995011D; // Optional, not required double g = 987.897; // No 'D' suffix, but OK because the // literal is a double by default Look for numeric literals that include a comma, for example, int x = 25,343; // Won't compile because of the comma Boolean Literals Boolean literals are the source code representation for boolean values. A boolean value can only be defined as true or false. Although in C (and some other languages) it is common to use numbers to represent true or false, this will not work in Java. Again, repeat after me, "Java is not C++." boolean t = true; // Legal boolean f = 0; // Compiler error! Be on the lookout for questions that use numbers where booleans are required. You might see an if test that uses a number, as in the following: int x = 1; if (x) { } // Compiler error! Character Literals A char literal is represented by a single character in single quotes. char a = 'a'; char b = '@'; You can also type in the Unicode value of the character, using the Unicode notation of prefixing the value with \u as follows: char letterN = '\u004E'; // The letter 'N' Remember, characters are just 16-bit unsigned integers under the hood. That means you can assign a number literal, assuming it will fit into the unsigned 16-bit range (65535 or less). For example, the following are all legal: char a = 0x892; // hexadecimal literal char b = 982; // int literal char c = (char)70000; // The cast is required; 70000 is // out of char range Literal Values for All Primitive Types (Exam Objectives 1.3 and 7.6) 179 180 Chapter 3: Assignments char d = (char) -98; // Ridiculous, but legal And the following are not legal and produce compiler errors: char e = -29; // Possible loss of precision; needs a cast char f = 70000 // Possible loss of precision; needs a cast You can also use an escape code if you want to represent a character that can't be typed in as a literal, including the characters for linefeed, newline, horizontal tab, backspace, and single quotes. char c = '\"'; // A double quote char d = '\n'; // A newline Literal Values for Strings A string literal is a source code representation of a value of a String object. For example, the following is an example of two ways to represent a string literal: String s = "Bill Joy"; System.out.println("Bill" + " Joy"); Although strings are not primitives, they're included in this section because they can be represented as literals—in other words, typed directly into code. The only other nonprimitive type that has a literal representation is an array, which we'll look at later in the chapter. Thread t = ??? // what literal value could possibly go here? Assignment Operators Assigning a value to a variable seems straightforward enough; you simply assign the stuff on the right side of the = to the variable on the left. Well, sure, but don't expect to be tested on something like this: x = 6; No, you won't be tested on the no-brainer (technical term) assignments. You will, however, be tested on the trickier assignments involving complex expressions and casting. We'll look at both primitive and reference variable assignments. But before we begin, let's back up and peek inside a variable. What is a variable? How are the variable and its value related? Variables are just bit holders, with a designated type. You can have an int holder, a double holder, a Button holder, and even a String[] holder. Within that holder is a bunch of bits representing the value. For primitives, the bits represent a numeric value (although we don't know what that bit pattern looks like for boolean, luckily, we don't care). A byte with a value of 6, for example, means that the bit pattern in the variable (the byte holder) is 00000110, representing the 8 bits. So the value of a primitive variable is clear, but what's inside an object holder? If you say, Button b = new Button(); what's inside the Button holder b? Is it the Button object? No! A variable referring to an object is just that—a reference variable. A reference variable bit holder contains bits representing a way to get to the object. We don't know what the format is. The way in which object references are stored is virtual-machine specific (it's a pointer to something, we just don't know what that something really is). All we can say for sure is that the variable's value is not the object, but rather a value representing a specific object on the heap. Or null. If the reference variable has not been assigned a value, or has been explicitly assigned a value of null, the variable holds bits representing—you guessed it—null. You can read Button b = null; as "The Button variable b is not referring to any object." So now that we know a variable is just a little box o' bits, we can get on with the work of changing those bits. We'll look first at assigning values to primitives, and finish with assignments to reference variables. Primitive Assignments The equal (=) sign is used for assigning a value to a variable, and it's cleverly named the assignment operator. There are actually 12 assignment operators, but only the five most commonly used are on the exam, and they are covered in Chapter 4. You can assign a primitive variable using a literal or the result of an expression. Assignment Operators (Exam Objectives 1.3 and 7.6) 181 182 Chapter 3: Assignments Take a look at the following: int x = 7; // literal assignment int y = x + 2; // assignment with an expression // (including a literal) int z = x * y; // assignment with an expression The most important point to remember is that a literal integer (such as 7) is always implicitly an int. Thinking back to Chapter 1, you'll recall that an int is a 32-bit value. No big deal if you're assigning a value to an int or a long variable, but what if you're assigning to a byte variable? After all, a byte-sized holder can't hold as many bits as an int-sized holder. Here's where it gets weird. The following is legal, byte b = 27; but only because the compiler automatically narrows the literal value to a byte. In other words, the compiler puts in the cast. The preceding code is identical to the following: byte b = (byte) 27; // Explicitly cast the int literal to a byte It looks as though the compiler gives you a break, and lets you take a shortcut with assignments to integer variables smaller than an int. (Everything we're saying about byte applies equally to char and short, both of which are smaller than an int.) We're not actually at the weird part yet, by the way. We know that a literal integer is always an int, but more importantly, the result of an expression involving anything int-sized or smaller is always an int. In other words, add two bytes together and you'll get an int—even if those two bytes are tiny. Multiply an int and a short and you'll get an int. Divide a short by a byte and you'll get…an int. OK, now we're at the weird part. Check this out: byte b = 3; // No problem, 3 fits in a byte byte c = 8; // No problem, 8 fits in a byte byte d = b + c; // Should be no problem, sum of the two bytes // fits in a byte The last line won't compile! You'll get an error something like this: TestBytes.java:5: possible loss of precision found : int required: byte byte c = a + b; ^ We tried to assign the sum of two bytes to a byte variable, the result of which (11) was definitely small enough to fit into a byte, but the compiler didn't care. It knew the rule about int-or-smaller expressions always resulting in an int. It would have compiled if we'd done the explicit cast: byte c = (byte) (a + b); Primitive Casting Casting lets you convert primitive values from one type to another. We mentioned primitive casting in the previous section, but now we're going to take a deeper look. (Object casting was covered in Chapter 2.) Casts can be implicit or explicit. An implicit cast means you don't have to write code for the cast; the conversion happens automatically. Typically, an implicit cast happens when you're doing a widening conversion. In other words, putting a smaller thing (say, a byte) into a bigger container (like an int). Remember those "possible loss of precision" compiler errors we saw in the assignments section? Those happened when we tried to put a larger thing (say, a long) into a smaller container (like a short). The large-value-into-small-container conversion is referred to as narrowing and requires an explicit cast, where you tell the compiler that you're aware of the danger and accept full responsibility. First we'll look at an implicit cast: int a = 100; long b = a; // Implicit cast, an int value always fits in a long An explicit casts looks like this: float a = 100.001f; int b = (int)a; // Explicit cast, the float could lose info Integer values may be assigned to a double variable without explicit casting, because any integer value can fit in a 64-bit double. The following line demonstrates this: Assignment Operators (Exam Objectives 1.3 and 7.6) 183 184 Chapter 3: Assignments double d = 100L; // Implicit cast In the preceding statement, a double is initialized with a long value (as denoted by the L after the numeric value). No cast is needed in this case because a double can hold every piece of information that a long can store. If, however, we want to assign a double value to an integer type, we're attempting a narrowing conversion and the compiler knows it: class Casting { public static void main(String [] args) { int x = 3957.229; // illegal } } If we try to compile the preceding code, we get an error something like: %javac Casting.java Casting.java:3: Incompatible type for declaration. Explicit cast needed to convert double to int. int x = 3957.229; // illegal 1 error In the preceding code, a floating-point value is being assigned to an integer variable. Because an integer is not capable of storing decimal places, an error occurs. To make this work, we'll cast the floating-point number into an int: class Casting { public static void main(String [] args) { int x = (int)3957.229; // legal cast System.out.println("int x = " + x); } } When you cast a floating-point number to an integer type, the value loses all the digits after the decimal. The preceding code will produce the following output: int x = 3957 We can also cast a larger number type, such as a long, into a smaller number type, such as a byte. Look at the following: Assignment Operators (Exam Objectives 1.3 and 7.6) 185 class Casting { public static void main(String [] args) { long l = 56L; byte b = (byte)l; System.out.println("The byte is " + b); } } The preceding code will compile and run fine. But what happens if the long value is larger than 127 (the largest number a byte can store)? Let's modify the code: class Casting { public static void main(String [] args) { long l = 130L; byte b = (byte)l; System.out.println("The byte is " + b); } } The code compiles fine, and when we run it we get the following: %java Casting The byte is -126 You don't get a runtime error, even when the value being narrowed is too large for the type. The bits to the left of the lower 8 just…go away. If the leftmost bit (the sign bit) in the byte (or any integer primitive) now happens to be a 1, the primitive will have a negative value. Exercise 3-1 Casting Primitives Create a float number type of any value, and assign it to a short using casting. 1. Declare a float variable: float f = 234.56F; 2. Assign the float to a short: short s = (short)f; 186 Chapter 3: Assignments Assigning Floating-Point Numbers Floating-point numbers have slightly different assignment behavior than integer types. First, you must know that every floating-point literal is implicitly a double (64 bits), not a float. So the literal 32.3, for example, is considered a double. If you try to assign a double to a float, the compiler knows you don't have enough room in a 32-bit float container to hold the precision of a 64-bit double, and it lets you know. The following code looks good, but won't compile: float f = 32.3; You can see that 32.3 should fit just fine into a float-sized variable, but the compiler won't allow it. In order to assign a floating-point literal to a float variable, you must either cast the value or append an f to the end of the literal. The following assignments will compile: float f = (float) 32.3; float g = 32.3f; float h = 32.3F; Assigning a Literal That Is Too Large for the Variable We'll also get a compiler error if we try to assign a literal value that the compiler knows is too big to fit into the variable. byte a = 128; // byte can only hold up to 127 The preceding code gives us an error something like TestBytes.java:5: possible loss of precision found : int required: byte byte a = 128; We can fix it with a cast: byte a = (byte) 128; But then what's the result? When you narrow a primitive, Java simply truncates the higher-order bits that won't fit. In other words, it loses all the bits to the left of the bits you're narrowing to. Let's take a look at what happens in the preceding code. There, 128 is the bit pattern 10000000. It takes a full 8 bits to represent 128. But because the literal 128 is an int, we actually get 32 bits, with the 128 living in the right-most (lower-order) 8 bits. So a literal 128 is actually 00000000000000000000000010000000 Take our word for it; there are 32 bits there. To narrow the 32 bits representing 128, Java simply lops off the leftmost (higherorder) 24 bits. We're left with just the 10000000. But remember that a byte is signed, with the leftmost bit representing the sign (and not part of the value of the variable). So we end up with a negative number (the 1 that used to represent 128 now represents the negative sign bit). Remember, to find out the value of a negative number using two's complement notation, you flip all of the bits and then add 1. Flipping the 8 bits gives us 01111111, and adding 1 to that gives us 10000000, or back to 128! And when we apply the sign bit, we end up with -128. You must use an explicit cast to assign 128 to a byte, and the assignment leaves you with the value -128. A cast is nothing more than your way of saying to the compiler, "Trust me. I'm a professional. I take full responsibility for anything weird that happens when those top bits are chopped off." That brings us to the compound assignment operators. The following will compile, byte b = 3; b += 7; // No problem - adds 7 to b (result is 10) and is equivalent to byte b = 3; b = (byte) (b + 7); // Won't compile without the // cast, since b + 7 results in an int The compound assignment operator += lets you add to the value of b, without putting in an explicit cast. In fact, +=, -=, *=, and /= will all put in an implicit cast. Assignment Operators (Exam Objectives 1.3 and 7.6) 187 Assigning One Primitive Variable to Another Primitive Variable When you assign one primitive variable to another, the contents of the right-hand variable are copied. For example, int a = 6; int b = a; This code can be read as, "Assign the bit pattern for the number 6 to the int variable a. Then copy the bit pattern in a, and place the copy into variable b." So, both variables now hold a bit pattern for 6, but the two variables have no other relationship. We used the variable a only to copy its contents. At this point, a and b have identical contents (in other words, identical values), but if we change the contents of either a or b, the other variable won't be affected. Take a look at the following example: class ValueTest { public static void main (String [] args) { int a = 10; // Assign a value to a System.out.println("a = " + a); int b = a; b = 30; System.out.println("a = " + a + " after change to b"); } } The output from this program is %java ValueTest a = 10 a = 10 after change to b Notice the value of a stayed at 10. The key point to remember is that even after you assign a to b, a and b are not referring to the same place in memory. The a and b variables do not share a single value; they have identical copies. Reference Variable Assignments You can assign a newly created object to an object reference variable as follows: Button b = new Button(); 188 Chapter 3: Assignments The preceding line does three key things: n Makes a reference variable named b, of type Button n Creates a new Button object on the heap n Assigns the newly created Button object to the reference variable b You can also assign null to an object reference variable, which simply means the variable is not referring to any object: Button c = null; The preceding line creates space for the Button reference variable (the bit holder for a reference value), but doesn't create an actual Button object. As we discussed in the last chapter, you can also use a reference variable to refer to any object that is a subclass of the declared reference variable type, as follows: public class Foo { public void doFooStuff() { } } public class Bar extends Foo { public void doBarStuff() { } } class Test { public static void main (String [] args) { Foo reallyABar = new Bar(); // Legal because Bar is a // subclass of Foo Bar reallyAFoo = new Foo(); // Illegal! Foo is not a // subclass of Bar } } The rule is that you can assign a subclass of the declared type, but not a superclass of the declared type. Remember, a Bar object is guaranteed to be able to do anything a Foo can do, so anyone with a Foo reference can invoke Foo methods even though the object is actually a Bar. In the preceding code, we see that Foo has a method doFooStuff() that someone with a Foo reference might try to invoke. If the object referenced by the Foo variable is really a Foo, no problem. But it's also no problem if the object is a Bar, since Bar inherited the doFooStuff() method. You can't make it work Assignment Operators (Exam Objectives 1.3 and 7.6) 189 in reverse, however. If somebody has a Bar reference, they're going to invoke doBarStuff(), but if the object is a Foo, it won't know how to respond. Variable Scope Once you've declared and initialized a variable, a natural question is "How long will this variable be around?" This is a question regarding the scope of variables. And not only is scope an important thing to understand in general, it also plays a big part in the exam. Let's start by looking at a class file: class Layout { // class static int s = 343; // static variable int x; // instance variable { x = 7; int x2 = 5; } // initialization block Layout() { x += 8; int x3 = 6;} // constructor void doStuff() { // method int y = 0; // local variable for(int z = 0; z < 4; z++) { // 'for' code block y += z + x; } } } As with variables in all Java programs, the variables in this program (s, x, x2, x3, y, and z) all have a scope: n s is a static variable. n x is an instance variable. n y is a local variable (sometimes called a "method local" variable). n z is a block variable. n x2 is an init block variable, a flavor of local variable. n x3 is a constructor variable, a flavor of local variable. For the purposes of discussing the scope of variables, we can say that there are four basic scopes: 190 Chapter 3: Assignments n Static variables have the longest scope; they are created when the class is loaded, and they survive as long as the class stays loaded in the JVM. n Instance variables are the next most long-lived; they are created when a new instance is created, and they live until the instance is removed. n Local variables are next; they live as long as their method remains on the stack. As we'll soon see, however, local variables can be alive, and still be "out of scope". n Block variables live only as long as the code block is executing. Scoping errors come in many sizes and shapes. One common mistake happens when a variable is shadowed and two scopes overlap. We'll take a detailed look at shadowing in a few pages. The most common reason for scoping errors is when you attempt to access a variable that is not in scope. Let's look at three common examples of this type of error: n Attempting to access an instance variable from a static context (typically from main() ). class ScopeErrors { int x = 5; public static void main(String[] args) { x++; // won't compile, x is an 'instance' variable } } n Attempting to access a local variable from a nested method. When a method, say go(), invokes another method, say go2(), go2() won't have access to go()'s local variables. While go2() is executing, go()'s local variables are still alive, but they are out of scope. When go2() completes, it is removed from the stack, and go() resumes execution. At this point, all of go()'s previously declared variables are back in scope. For example: class ScopeErrors { public static void main(String [] args) { ScopeErrors s = new ScopeErrors(); s.go(); } void go() { int y = 5; Assignment Operators (Exam Objectives 1.3 and 7.6) 191 go2(); y++; // once go2() completes, y is back in scope } void go2() { y++; // won't compile, y is local to go() } } n Attempting to use a block variable after the code block has completed. It's very common to declare and use a variable within a code block, but be careful not to try to use the variable once the block has completed: void go3() { for(int z = 0; z < 5; z++) { boolean test = false; if(z == 3) { test = true; break; } } System.out.print(test); // 'test' is an ex-variable, // it has ceased to be... } In the last two examples, the compiler will say something like this: cannot find symbol This is the compiler's way of saying, "That variable you just tried to use? Well, it might have been valid in the distant past (like one line of code ago), but this is Internet time baby, I have no memory of such a variable." 192 Chapter 3: Assignments Pay extra attention to code block scoping errors. You might see them in switches, try-catches, for, do, and while loops. Using a Variable or Array Element That Is Uninitialized and Unassigned Java gives us the option of initializing a declared variable or leaving it uninitialized. When we attempt to use the uninitialized variable, we can get different behavior depending on what type of variable or array we are dealing with (primitives or objects). The behavior also depends on the level (scope) at which we are declaring our variable. An instance variable is declared within the class but outside any method or constructor, whereas a local variable is declared within a method (or in the argument list of the method). Local variables are sometimes called stack, temporary, automatic, or method variables, but the rules for these variables are the same regardless of what you call them. Although you can leave a local variable uninitialized, the compiler complains if you try to use a local variable before initializing it with a value, as we shall see. Primitive and Object Type Instance Variables Instance variables (also called member variables) are variables defined at the class level. That means the variable declaration is not made within a method, constructor, or any other initializer block. Instance variables are initialized to a default value each time a new instance is created, although they may be given an explicit value after the object's super-constructors have completed. Table 3-1 lists the default values for primitive and object types. Using a Variable or Array Element That Is Uninitialized and Unassigned (Exam Objectives 1.3 & 7.6) 193 Variable Type Default Value Object reference null (not referencing any object) byte, short, int, long 0 float, double 0.0 boolean false char '\u0000' table 3-1 Default Values for Primitives and Reference Types Primitive Instance Variables In the following example, the integer year is defined as a class member because it is within the initial curly braces of the class and not within a method's curly braces: public class BirthDate { int year; // Instance variable public static void main(String [] args) { BirthDate bd = new BirthDate(); bd.showYear(); } public void showYear() { System.out.println("The year is " + year); } } When the program is started, it gives the variable year a value of zero, the default value for primitive number instance variables. It's a good idea to initialize all your variables, even if you're assigning them with the default value. Your code will be easier to read; programmers who have to maintain your code (after you win the lottery and move to Tahiti) will be grateful. Object Reference Instance Variables When compared with uninitialized primitive variables, object references that aren't initialized are a completely different story. Let's look at the following code: public class Book { private String title; // instance reference variable public String getTitle() { return title; } public static void main(String [] args) { Book b = new Book(); System.out.println("The title is " + b.getTitle()); } } 194 Chapter 3: Assignments Using a Variable or Array Element That Is Uninitialized and Unassigned (Exam Objectives 1.3 & 7.6) 195 This code will compile fine. When we run it, the output is The title is null The title variable has not been explicitly initialized with a String assignment, so the instance variable value is null. Remember that null is not the same as an empty String (""). A null value means the reference variable is not referring to any object on the heap. The following modification to the Book code runs into trouble: public class Book { private String title; // instance reference variable public String getTitle() { return title; } public static void main(String [] args) { Book b = new Book(); String s = b.getTitle(); // Compiles and runs String t = s.toLowerCase(); // Runtime Exception! } } When we try to run the Book class, the JVM will produce something like this: Exception in thread "main" java.lang.NullPointerException at Book.main(Book.java:9) We get this error because the reference variable title does not point (refer) to an object. We can check to see whether an object has been instantiated by using the keyword null, as the following revised code shows: public class Book { private String title; // instance reference variable public String getTitle() { return title; } public static void main(String [] args) { Book b = new Book(); String s = b.getTitle(); // Compiles and runs if (s != null) { String t = s.toLowerCase(); } } } The preceding code checks to make sure the object referenced by the variable s is not null before trying to use it. Watch out for scenarios on the exam where you might have to trace back through the code to find out whether an object reference will have a value of null. In the preceding code, for example, you look at the instance variable declaration for title, see that there's no explicit initialization, recognize that the title variable will be given the default value of null, and then realize that the variable s will also have a value of null. Remember, the value of s is a copy of the value of title (as returned by the getTitle() method), so if title is a null reference, s will be too. Array Instance Variables Later in this chapter we'll be taking a very detailed look at declaring, constructing, and initializing arrays and multidimensional arrays. For now, we're just going to look at the rule for an array element's default values. An array is an object; thus, an array instance variable that's declared but not explicitly initialized will have a value of null, just as any other object reference instance variable. But…if the array is initialized, what happens to the elements contained in the array? All array elements are given their default values—the same default values that elements of that type get when they're instance variables. The bottom line: Array elements are always, always, always given default values, regardless of where the array itself is declared or instantiated. If we initialize an array, object reference elements will equal null if they are not initialized individually with values. If primitives are contained in an array, they will be given their respective default values. For example, in the following code, the array year will contain 100 integers that all equal zero by default: public class BirthDays { static int [] year = new int[100]; public static void main(String [] args) { for(int i=0;i<100;i++) System.out.println("year[" + i + "] = " + year[i]); } } When the preceding code runs, the output indicates that all 100 integers in the array equal zero. 196 Chapter 3: Assignments Local (Stack, Automatic) Primitives and Objects (Exam Objectives 1.3 and 7.6) 197 Local (Stack, Automatic) Primitives and Objects Local variables are defined within a method, and they include a method's parameters. Local Primitives In the following time travel simulator, the integer year is defined as an automatic variable because it is within the curly braces of a method. public class TimeTravel { public static void main(String [] args) { int year = 2050; System.out.println("The year is " + year); } } Local variables, including primitives, always, always, always must be initialized before you attempt to use them (though not necessarily on the same line of code). Java does not give local variables a default value; you must explicitly initialize them with a value, as in the preceding example. If you try to use an uninitialized primitive in your code, you'll get a compiler error: public class TimeTravel { public static void main(String [] args) { int year; // Local variable (declared but not initialized) System.out.println("The year is " + year); // Compiler error } } “Automatic” is just another term for “local variable.” It does not mean the automatic variable is automatically assigned a value! The opposite is true. An automatic variable must be assigned a value in the code, or the compiler will complain. Compiling produces output something like this: %javac TimeTravel.java TimeTravel.java:4: Variable year may not have been initialized. System.out.println("The year is " + year); 1 error To correct our code, we must give the integer year a value. In this updated example, we declare it on a separate line, which is perfectly valid: public class TimeTravel { public static void main(String [] args) { int year; // Declared but not initialized int day; // Declared but not initialized System.out.println("You step into the portal."); year = 2050; // Initialize (assign an explicit value) System.out.println("Welcome to the year " + year); } } Notice in the preceding example we declared an integer called day that never gets initialized, yet the code compiles and runs fine. Legally, you can declare a local variable without initializing it as long as you don't use the variable, but let's face it, if you declared it, you probably had a reason (although we have heard of programmers declaring random local variables just for sport, to see if they can figure out how and why they're being used). The compiler can't always tell whether a local variable has been initialized before use. For example, if you initialize within a logically conditional block (in other words, a code block that may not run, such as an if block or for loop without a literal value of true or false in the test), the compiler knows that the initialization might not happen, and can produce an error. The following code upsets the compiler: public class TestLocal { public static void main(String [] args) { int x; if (args[0] != null) { // assume you know this will // always be true 198 Chapter 3: Assignments x = 7; // compiler can't tell that this // statement will run } int y = x; // the compiler will choke here } } The compiler will produce an error something like this: TestLocal.java:9: variable x might not have been initialized Because of the compiler-can't-tell-for-certain problem, you will sometimes need to initialize your variable outside the conditional block, just to make the compiler happy. You know why that's important if you've seen the bumper sticker, "When the compiler's not happy, ain't nobody happy." Local Object References Objects references, too, behave differently when declared within a method rather than as instance variables. With instance variable object references, you can get away with leaving an object reference uninitialized, as long as the code checks to make sure the reference isn't null before using it. Remember, to the compiler, null is a value. You can't use the dot operator on a null reference, because there is no object at the other end of it, but a null reference is not the same as an uninitialized reference. Locally declared references can't get away with checking for null before use, unless you explicitly initialize the local variable to null. The compiler will complain about the following code: import java.util.Date; public class TimeTravel { public static void main(String [] args) { Date date; if (date == null) System.out.println("date is null"); } } Compiling the code results in an error similar to the following: Local (Stack, Automatic) Primitives and Objects (Exam Objectives 1.3 & 7.6) 199 %javac TimeTravel.java TimeTravel.java:5: Variable date may not have been initialized. if (date == null) 1 error Instance variable references are always given a default value of null, until explicitly initialized to something else. But local references are not given a default value; in other words, they aren't null. If you don't initialize a local reference variable, then by default, its value is…well that's the whole point—it doesn't have any value at all! So we'll make this simple: Just set the darn thing to null explicitly, until you're ready to initialize it to something else. The following local variable will compile properly: Date date = null; // Explicitly set the local reference // variable to null Local Arrays Just like any other object reference, array references declared within a method must be assigned a value before use. That just means you must declare and construct the array. You do not, however, need to explicitly initialize the elements of an array. We've said it before, but it's important enough to repeat: array elements are given their default values (0, false, null, '\u0000', etc.) regardless of whether the array is declared as an instance or local variable. The array object itself, however, will not be initialized if it's declared locally. In other words, you must explicitly initialize an array reference if it's declared and used within a method, but at the moment you construct an array object, all of its elements are assigned their default values. Assigning One Reference Variable to Another With primitive variables, an assignment of one variable to another means the contents (bit pattern) of one variable are copied into another. Object reference variables work exactly the same way. The contents of a reference variable are a bit pattern, so if you assign reference variable a to reference variable b, the bit pattern in a is copied and the new copy is placed into b. (Some people have created a game around counting how many times we use the word copy in this chapter…this copy concept is a biggie!) If we assign an existing instance of an object to a new reference variable, then two reference variables will hold the same bit pattern—a bit pattern referring to a specific object on the heap. Look at the following code: 200 Chapter 3: Assignments import java.awt.Dimension; class ReferenceTest { public static void main (String [] args) { Dimension a = new Dimension(5,10); System.out.println("a.height = " + a.height); Dimension b = a; b.height = 30; System.out.println("a.height = " + a.height + " after change to b"); } } In the preceding example, a Dimension object a is declared and initialized with a width of 5 and a height of 10. Next, Dimension b is declared, and assigned the value of a. At this point, both variables (a and b) hold identical values, because the contents of a were copied into b. There is still only one Dimension object—the one that both a and b refer to. Finally, the height property is changed using the b reference. Now think for a minute: is this going to change the height property of a as well? Let's see what the output will be: %java ReferenceTest a.height = 10 a.height = 30 after change to b From this output, we can conclude that both variables refer to the same instance of the Dimension object. When we made a change to b, the height property was also changed for a. One exception to the way object references are assigned is String. In Java, String objects are given special treatment. For one thing, String objects are immutable; you can't change the value of a String object (lots more on this concept in Chapter 6). But it sure looks as though you can. Examine the following code: class StringTest { public static void main(String [] args) { String x = "Java"; // Assign a value to x String y = x; // Now y and x refer to the same // String object System.out.println("y string = " + y); x = x + " Bean"; // Now modify the object using // the x reference Local (Stack, Automatic) Primitives and Objects (Exam Objectives 1.3 and 7.6) 201 System.out.println("y string = " + y); } } You might think String y will contain the characters Java Bean after the variable x is changed, because Strings are objects. Let's see what the output is: %java StringTest y string = Java y string = Java As you can see, even though y is a reference variable to the same object that x refers to, when we change x, it doesn't change y! For any other object type, where two references refer to the same object, if either reference is used to modify the object, both references will see the change because there is still only a single object. But any time we make any changes at all to a String, the VM will update the reference variable to refer to a different object. The different object might be a new object, or it might not, but it will definitely be a different object. The reason we can't say for sure whether a new object is created is because of the String constant pool, which we'll cover in Chapter 6. You need to understand what happens when you use a String reference variable to modify a string: n A new string is created (or a matching String is found in the String pool), leaving the original String object untouched. n The reference used to modify the String (or rather, make a new String by modifying a copy of the original) is then assigned the brand new String object. So when you say 1. String s = "Fred"; 2. String t = s; // Now t and s refer to the same // String object 3. t.toUpperCase(); // Invoke a String method that changes // the String you haven't changed the original String object created on line 1. When line 2 completes, both t and s reference the same String object. But when line 3 runs, rather than modifying the object referred to by t (which is the one and only String 202 Chapter 3: Assignments object up to this point), a brand new String object is created. And then abandoned. Because the new String isn't assigned to a String variable, the newly created String (which holds the string "FRED") is toast. So while two String objects were created in the preceding code, only one is actually referenced, and both t and s refer to it. The behavior of Strings is extremely important in the exam, so we'll cover it in much more detail in Chapter 6. CERTIFICATION OBJECTIVE Passing Variables into Methods (Exam Objective 7.3) 7.3 Determine the effect upon object references and primitive values when they are passed into methods that perform assignments or other modifying operations on the parameters. Methods can be declared to take primitives and/or object references. You need to know how (or if) the caller's variable can be affected by the called method. The difference between object reference and primitive variables, when passed into methods, is huge and important. To understand this section, you'll need to be comfortable with the assignments section covered in the first part of this chapter. Passing Object Reference Variables When you pass an object variable into a method, you must keep in mind that you're passing the object reference, and not the actual object itself. Remember that a reference variable holds bits that represent (to the underlying VM) a way to get to a specific object in memory (on the heap). More importantly, you must remember that you aren't even passing the actual reference variable, but rather a copy of the reference variable. A copy of a variable means you get a copy of the bits in that variable, so when you pass a reference variable, you're passing a copy of the bits representing how to get to a specific object. In other words, both the caller and the called method will now have identical copies of the reference, and thus both will refer to the same exact (not a copy) object on the heap. For this example, we'll use the Dimension class from the java.awt package: 1. import java.awt.Dimension; 2. class ReferenceTest { Passing Object Reference Variables (Exam Objective 7.3) 203 3. public static void main (String [] args) { 4. Dimension d = new Dimension(5,10); 5. ReferenceTest rt = new ReferenceTest(); 6. System.out.println("Before modify() d.height = " + d.height); 7. rt.modify(d); 8. System.out.println("After modify() d.height = " + d.height); 9. } 10. void modify(Dimension dim) { 11. dim.height = dim.height + 1; 12. System.out.println("dim = " + dim.height); 13. } 14. } When we run this class, we can see that the modify() method was indeed able to modify the original (and only) Dimension object created on line 4. C:\Java Projects\Reference>java ReferenceTest Before modify() d.height = 10 dim = 11 After modify() d.height = 11 Notice when the Dimension object on line 4 is passed to the modify() method, any changes to the object that occur inside the method are being made to the object whose reference was passed. In the preceding example, reference variables d and dim both point to the same object. Does Java Use Pass-By-Value Semantics? If Java passes objects by passing the reference variable instead, does that mean Java uses pass-by-reference for objects? Not exactly, although you'll often hear and read that it does. Java is actually pass-by-value for all variables running within a single VM. Pass-by-value means pass-by-variable-value. And that means, pass-by-copy-ofthe- variable! (There's that word copy again!) It makes no difference if you're passing primitive or reference variables, you are always passing a copy of the bits in the variable. So for a primitive variable, you're passing a copy of the bits representing the value. For example, if you pass an int variable with the value of 3, you're passing a copy of the bits representing 3. The called method then gets its own copy of the value, to do with it what it likes. 204 Chapter 3: Assignments And if you're passing an object reference variable, you're passing a copy of the bits representing the reference to an object. The called method then gets its own copy of the reference variable, to do with it what it likes. But because two identical reference variables refer to the exact same object, if the called method modifies the object (by invoking setter methods, for example), the caller will see that the object the caller's original variable refers to has also been changed. In the next section, we'll look at how the picture changes when we're talking about primitives. The bottom line on pass-by-value: the called method can't change the caller's variable, although for object reference variables, the called method can change the object the variable referred to. What's the difference between changing the variable and changing the object? For object references, it means the called method can't reassign the caller's original reference variable and make it refer to a different object, or null. For example, in the following code fragment, void bar() { Foo f = new Foo(); doStuff(f); } void doStuff(Foo g) { g.setName("Boo"); g = new Foo(); } reassigning g does not reassign f! At the end of the bar() method, two Foo objects have been created, one referenced by the local variable f and one referenced by the local (argument) variable g. Because the doStuff() method has a copy of the reference variable, it has a way to get to the original Foo object, for instance to call the setName() method. But, the doStuff() method does not have a way to get to the f reference variable. So doStuff() can change values within the object f refers to, but doStuff() can't change the actual contents (bit pattern) of f. In other words, doStuff() can change the state of the object that f refers to, but it can't make f refer to a different object! Passing Primitive Variables Let's look at what happens when a primitive variable is passed to a method: class ReferenceTest { public static void main (String [] args) { Passing Primitive Variables (Exam Objective 7.3) 205 int a = 1; ReferenceTest rt = new ReferenceTest(); System.out.println("Before modify() a = " + a); rt.modify(a); System.out.println("After modify() a = " + a); } void modify(int number) { number = number + 1; System.out.println("number = " + number); } } In this simple program, the variable a is passed to a method called modify(), which increments the variable by 1. The resulting output looks like this: Before modify() a = 1 number = 2 After modify() a = 1 Notice that a did not change after it was passed to the method. Remember, it was a copy of a that was passed to the method. When a primitive variable is passed to a method, it is passed by value, which means pass-by-copy-of-the-bits-in-the-variable. 206 Chapter 3: Assignments The Shadowy World of Variables Just when you think you’ve got it all figured out, you see a piece of code with variables not behaving the way you think they should. You might have stumbled into code with a shadowed variable. You can shadow a variable in several ways. We’ll look at the one most likely to trip you up: hiding an instance variable by shadowing it with a local variable. Shadowing involves redeclaring a variable that’s already been declared somewhere else. The effect of shadowing is to hide the previously declared variable in such a way that it may look as though you’re using the hidden variable, but you’re actually using the shadowing variable. You might find reasons to shadow a variable intentionally, but typically it happens by accident and causes hard-tofind bugs. On the exam, you can expect to see questions where shadowing plays a role. FROM THE CLASSROOM Passing Primitive Variables (Exam Objective 7.3) 207 FROM THE CLASSROOM You can shadow an instance variable by declaring a local variable of the same name, either directly or as part of an argument: class Foo { static int size = 7; static void changeIt(int size) { size = size + 200; System.out.println("size in changeIt is " + size); } public static void main (String [] args) { Foo f = new Foo(); System.out.println("size = " + size); changeIt(size); System.out.println("size after changeIt is " + size); } } The preceding code appears to change the size instance variable in the changeIt() method, but because changeIt() has a parameter named size, the local size variable is modified while the instance variable size is untouched. Running class Foo prints %java Foo size = 7 size in changeIt is 207 size after changeIt is 7 Things become more interesting when the shadowed variable is an object reference, rather than a primitive: class Bar { int barNum = 28; } 208 Chapter 3: Assignments class Foo { Bar myBar = new Bar(); void changeIt(Bar myBar) { myBar.barNum = 99; System.out.println("myBar.barNum in changeIt is " + myBar.barNum); myBar = new Bar(); myBar.barNum = 420; System.out.println("myBar.barNum in changeIt is now " + myBar.barNum); } public static void main (String [] args) { Foo f = new Foo(); System.out.println("f.myBar.barNum is " + f.myBar.barNum); f.changeIt(f.myBar); System.out.println("f.myBar.barNum after changeIt is " + f.myBar.barNum); } } The preceding code prints out this: f.myBar.barNum is 28 myBar.barNum in changeIt is 99 myBar.barNum in changeIt is now 420 f.myBar.barNum after changeIt is 99 You can see that the shadowing variable (the local parameter myBar in changeIt()) can still affect the myBar instance variable, because the myBar parameter receives a reference to the same Bar object. But when the local myBar is reassigned a new Bar object, which we then modify by changing its barNum value, Foo’s original myBar instance variable is untouched. FROM THE CLASSROOM CERTIFICATION OBJECTIVE Array Declaration, Construction, and Initialization (Exam Objective 1.3) 1.3 Develop code that declares, initializes, and uses primitives, arrays, enums, and objects as static, instance, and local variables. Also, use legal identifiers for variable names. Arrays are objects in Java that store multiple variables of the same type. Arrays can hold either primitives or object references, but the array itself will always be an object on the heap, even if the array is declared to hold primitive elements. In other words, there is no such thing as a primitive array, but you can make an array of primitives. For this objective, you need to know three things: n How to make an array reference variable (declare) n How to make an array object (construct) n How to populate the array with elements (initialize) There are several different ways to do each of those, and you need to know about all of them for the exam. Arrays are efficient, but most of the time you'll want to use one of the Collection types from java.util (including HashMap, ArrayList, TreeSet). Collection classes offer more flexible ways to access an object (for insertion, deletion, and so on) and unlike arrays, can expand or contract dynamically as you add or remove elements (they're really managed arrays, since they use arrays behind the scenes). There's a Collection type for a wide range of needs. Do you need a fast sort? A group of objects with no duplicates? A way to access a name/value pair? A linked list? Chapter 7 covers them in more detail. Declaring an Array Arrays are declared by stating the type of element the array will hold, which can be an object or a primitive, followed by square brackets to the left or right of the identifier. Declaring an Array (Exam Objective 1.3) 209 Declaring an array of primitives: int[] key; // brackets before name (recommended) int key []; // brackets after name (legal but less readable) // spaces between the name and [] legal, but bad Declaring an array of object references: Thread[] threads; // Recommended Thread threads[]; // Legal but less readable When declaring an array reference, you should always put the array brackets immediately after the declared type, rather than after the identifier (variable name). That way, anyone reading the code can easily tell that, for example, key is a reference to an int array object, and not an int primitive. We can also declare multidimensional arrays, which are in fact arrays of arrays. This can be done in the following manner: String[][][] occupantName; // recommended String[] ManagerName []; // yucky, but legal The first example is a three-dimensional array (an array of arrays of arrays) and the second is a two-dimensional array. Notice in the second example we have one square bracket before the variable name and one after. This is perfectly legal to the compiler, proving once again that just because it's legal doesn't mean it's right. It is never legal to include the size of the array in your declaration. Yes, we know you can do that in some other languages, which is why you might see a question or two that include code similar to the following: int[5] scores; The preceding code won't make it past the compiler. Remember, the JVM doesn't allocate space until you actually instantiate the array object. That's when size matters. Constructing an Array Constructing an array means creating the array object on the heap (where all objects live)—i.e., doing a new on the array type. To create an array object, Java must know 210 Chapter 3: Assignments how much space to allocate on the heap, so you must specify the size of the array at creation time. The size of the array is the number of elements the array will hold. Constructing One-Dimensional Arrays The most straightforward way to construct an array is to use the keyword new followed by the array type, with a bracket specifying how many elements of that type the array will hold. The following is an example of constructing an array of type int: int[] testScores; // Declares the array of ints testScores = new int[4]; // constructs an array and assigns it // the testScores variable The preceding code puts one new object on the heap—an array object holding four elements—with each element containing an int with a default value of 0. Think of this code as saying to the compiler, "Create an array object that will hold four ints, and assign it to the reference variable named testScores. Also, go ahead and set each int element to zero. Thanks." (The compiler appreciates good manners.) Figure 3-2 shows the testScores array on the heap, after construction. You can also declare and construct an array in one statement as follows: int[] testScores = new int[4]; This single statement produces the same result as the two previous statements. Arrays of object types can be constructed in the same way: Thread[] threads = new Thread[5]; Constructing an Array (Exam Objective 1.3) 211 Figure 3-2 A one-dimensional array on the Heap 212 Chapter 3: Assignments Remember that—despite how the code appears—the Thread constructor is not being invoked. We're not creating a Thread instance, but rather a single Thread array object. After the preceding statement, there are still no actual Thread objects! Remember, arrays must always be given a size at the time they are constructed. The JVM needs the size to allocate the appropriate space on the heap for the new array object. It is never legal, for example, to do the following: int[] carList = new int[]; // Will not compile; needs a size So don't do it, and if you see it on the test, run screaming toward the nearest answer marked "Compilation fails." In addition to being constructed with new, arrays can also be created using a kind of syntax shorthand that creates the array while simultaneously initializing the array elements to values supplied in code (as opposed to default values). We'll look at that in the next section. For now, understand that because of these syntax shortcuts, objects can still be created even without you ever using or seeing the keyword new. Think carefully about how many objects are on the heap after a code statement or block executes. The exam will expect you to know, for example, that the preceding code produces just one object (the array assigned to the reference variable named threads). The single object referenced by threads holds five Thread reference variables, but no Thread objects have been created or assigned to those references. You may see the words "construct", "create", and "instantiate" used interchangeably. They all mean, “An object is built on the heap.” This also implies that the object’s constructor runs, as a result of the construct/create/instantiate code. You can say with certainty, for example, that any code that uses the keyword new, will (if it runs successfully) cause the class constructor and all superclass constructors to run. Constructing Multidimensional Arrays Multidimensional arrays, remember, are simply arrays of arrays. So a twodimensional array of type int is really an object of type int array (int []), with each element in that array holding a reference to another int array. The second dimension holds the actual int primitives. The following code declares and constructs a two-dimensional array of type int: int[][] myArray = new int[3][]; Notice that only the first brackets are given a size. That's acceptable in Java, since the JVM needs to know only the size of the object assigned to the variable myArray. Figure 3-3 shows how a two-dimensional int array works on the heap. Constructing an Array (Exam Objective 1.3) 213 Figure 3-3 A two-dimensional array on the Heap Initializing an Array Initializing an array means putting things into it. The "things" in the array are the array's elements, and they're either primitive values (2, x, false, and so on), or objects referred to by the reference variables in the array. If you have an array of objects (as opposed to primitives), the array doesn't actually hold the objects, just as any other nonprimitive variable never actually holds the object, but instead holds a reference to the object. But we talk about arrays as, for example, "an array of five strings," even though what we really mean is, "an array of five references to String objects." Then the big question becomes whether or not those references are actually pointing (oops, this is Java, we mean referring) to real String objects, or are simply null. Remember, a reference that has not had an object assigned to it is a null reference. And if you try to actually use that null reference by, say, applying the dot operator to invoke a method on it, you'll get the infamous NullPointerException. The individual elements in the array can be accessed with an index number. The index number always begins with zero, so for an array of ten objects the index numbers will run from 0 through 9. Suppose we create an array of three Animals as follows: Animal [] pets = new Animal[3]; We have one array object on the heap, with three null references of type Animal, but we don't have any Animal objects. The next step is to create some Animal objects and assign them to index positions in the array referenced by pets: pets[0] = new Animal(); pets[1] = new Animal(); pets[2] = new Animal(); This code puts three new Animal objects on the heap and assigns them to the three index positions (elements) in the pets array. 214 Chapter 3: Assignments Look for code that tries to access an out-of-range array index. For example, if an array has three elements, trying to access the [3] element will raise an ArrayIndexOutOfBoundsException, because in an array of three elements, the legal index values are 0, 1, and 2. You also might see an attempt to use a negative number as an array index. The following are examples of legal and illegal array access attempts. Be sure to recognize that these cause runtime exceptions and not compiler errors! A two-dimensional array (an array of arrays) can be initialized as follows: int[][] scores = new int[3][]; // Declare and create an array holding three references // to int arrays scores[0] = new int[4]; // the first element in the scores array is an int array // of four int elements scores[1] = new int[6]; // the second element in the scores array is an int array // of six int elements scores[2] = new int[1]; // the third element in the scores array is an int array // of one int element Initializing Elements in a Loop Array objects have a single public variable, length that gives you the number of elements in the array. The last index value, then, is always one less than the length. For example, if the length of an array is 4, the index values are from 0 through 3. Often, you'll see array elements initialized in a loop as follows: Initializing an Array (Exam Objective 1.3) 215 Nearly all of the exam questions list both runtime exception and compiler error as possible answers. int[] x = new int[5]; x[4] = 2; // OK, the last element is at index 4 x[5] = 3; // Runtime exception. There is no element at index 5! int[] z = new int[2]; int y = -3; z[y] = 4; // Runtime exception.; y is a negative number These can be hard to spot in a complex loop, but that’s where you’re most likely to see array index problems in exam questions. 216 Chapter 3: Assignments Dog[] myDogs = new Dog[6]; // creates an array of 6 // Dog references for (int x = 0; x < myDogs.length; x++) { myDogs[x] = new Dog(); // assign a new Dog to the // index position x } The length variable tells us how many elements the array holds, but it does not tell us whether those elements have been initialized. As we'll cover in Chapter 5, as of Java 5, we could have written the for loop without using the length variable: for(Dog d : myDogs) d = new Dog(); Declaring, Constructing, and Initializing on One Line You can use two different array-specific syntax shortcuts to both initialize (put explicit values into an array's elements) and construct (instantiate the array object itself) in a single statement. The first is used to declare, create, and initialize in one statement as follows: 1. int x = 9; 2. int[] dots = {6,x,8}; Line 2 in the preceding code does four things: n Declares an int array reference variable named dots. n Creates an int array with a length of three (three elements). n Populates the array's elements with the values 6, 9, and 8. n Assigns the new array object to the reference variable dots. The size (length of the array) is determined by the number of comma-separated items between the curly braces. The code is functionally equivalent to the following longer code: int[] dots; dots = new int[3]; int x = 9; dots[0] = 6; dots[1] = x; dots[2] = 8; This begs the question, "Why would anyone use the longer way?" One reason come to mind. You might not know—at the time you create the array—the values that will be assigned to the array's elements. This array shortcut alone (combined with the stimulating prose) is worth the price of this book. With object references rather than primitives, it works exactly the same way: Dog puppy = new Dog("Frodo"); Dog[] myDogs = {puppy, new Dog("Clover"), new Dog("Aiko")}; The preceding code creates one Dog array, referenced by the variable myDogs, with a length of three elements. It assigns a previously created Dog object (assigned to the reference variable puppy) to the first element in the array. It also creates two new Dog objects (Clover and Aiko), and adds them to the last two Dog reference variable elements in the myDogs array. Figure 3-4 shows the result. Initializing an Array (Exam Objective 1.3) 217 Figure 3-4 Declaring, constructing, and initializing an array of objects 218 Chapter 3: Assignments You can also use the shortcut syntax with multidimensional arrays, as follows: int[][] scores = {{5,2,4,7}, {9,2}, {3,4}}; The preceding code creates a total of four objects on the heap. First, an array of int arrays is constructed (the object that will be assigned to the scores reference variable). The scores array has a length of three, derived from the number of items (comma-separated) between the outer curly braces. Each of the three elements in the scores array is a reference variable to an int array, so the three int arrays are constructed and assigned to the three elements in the scores array. The size of each of the three int arrays is derived from the number of items within the corresponding inner curly braces. For example, the first array has a length of four, the second array has a length of two, and the third array has a length of two. So far, we have four objects: one array of int arrays (each element is a reference to an int array), and three int arrays (each element in the three int arrays is an int value). Finally, the three int arrays are initialized with the actual int values within the inner curly braces. Thus, the first int array contains the values 5, 2, 4, and 7. The following code shows the values of some of the elements in this two-dimensional array: scores[0] // an array of four ints scores[1] // an array of 2 ints scores[2] // an array of 2 ints scores[0][1] // the int value 2 scores[2][1] // the int value 4 Figure 3-5 shows the result of declaring, constructing, and initializing a twodimensional array in one statement. Constructing and Initializing an Anonymous Array The second shortcut is called "anonymous array creation" and can be used to construct and initialize an array, and then assign the array to a previously declared array reference variable: int[] testScores; testScores = new int[] {4,7,2}; The preceding code creates a new int array with three elements, initializes the three elements with the values 4, 7, and 2, and then assigns the new array to the previously declared int array reference variable testScores. We call this anonymous array creation because with this syntax you don't even need to assign the new array to anything. Maybe you're wondering, "What good is an array if you don't assign it to a reference variable?" You can use it to create a just-in-time array to use, for example, as an argument to a method that takes an array parameter. The following code demonstrates a just-in-time array argument: Initializing an Array (Exam Objective 1.3) 219 Figure 3-5 Declaring, constructing, and initializing a twodimensional array 220 Chapter 3: Assignments public class Foof { void takesAnArray(int [] someArray) { // use the array parameter } public static void main (String [] args) { Foof f = new Foof(); f.takesAnArray(new int[] {7,7,8,2,5}); // we need an array // argument } } Legal Array Element Assignments What can you put in a particular array? For the exam, you need to know that arrays can have only one declared type (int [], Dog[], String [], and so on), but that doesn't necessarily mean that only objects or primitives of the declared type can be assigned to the array elements. And what about the array reference itself? What kind of array object can be assigned to a particular array reference? For the exam, you'll need to know the answers to all of these questions. And, as if by magic, we're actually covering those very same topics in the following sections. Pay attention. Arrays of Primitives Primitive arrays can accept any value that can be promoted implicitly to the declared type of the array. For example, an int array can hold any value that can fit into a 32-bit int variable. Thus, the following code is legal: Remember that you do not specify a size when using anonymous array creation syntax. The size is derived from the number of items (comma-separated) between the curly braces. Pay very close attention to the array syntax used in exam questions (and there will be a lot of them). You might see syntax such as new Object[3] {null, new Object(), new Object()}; // not legal;size must not be specified int[] weightList = new int[5]; byte b = 4; char c = 'c'; short s = 7; weightList[0] = b; // OK, byte is smaller than int weightlist[1] = c; // OK, char is smaller than int weightList[2] = s; // OK, short is smaller than int Arrays of Object References If the declared array type is a class, you can put objects of any subclass of the declared type into the array. For example, if Subaru is a subclass of Car, you can put both Subaru objects and Car objects into an array of type Car as follows: class Car {} class Subaru extends Car {} class Ferrari extends Car {} ... Car [] myCars = {new Subaru(), new Car(), new Ferrari()}; It helps to remember that the elements in a Car array are nothing more than Car reference variables. So anything that can be assigned to a Car reference variable can be legally assigned to a Car array element. If the array is declared as an interface type, the array elements can refer to any instance of any class that implements the declared interface. The following code demonstrates the use of an interface as an array type: interface Sporty { void beSporty(); } class Ferrari extends Car implements Sporty { public void beSporty() { // implement cool sporty method in a Ferrari-specific way } } class RacingFlats extends AthleticShoe implements Sporty { public void beSporty() { // implement cool sporty method in a RacingShoe-specific way } Initializing an Array (Exam Objective 1.3) 221 222 Chapter 3: Assignments } class GolfClub { } class TestSportyThings { public static void main (String [] args) { Sporty[] sportyThings = new Sporty [3]; sportyThings[0] = new Ferrari(); // OK, Ferrari // implements Sporty sportyThings[1] = new RacingFlats(); // OK, RacingFlats // implements Sporty sportyThings[2] = new GolfClub(); // Not OK; GolfClub does not implement Sporty // I don't care what anyone says } } The bottom line is this: any object that passes the "IS-A" test for the declared array type can be assigned to an element of that array. Array Reference Assignments for One-Dimensional Arrays For the exam, you need to recognize legal and illegal assignments for array reference variables. We're not talking about references in the array (in other words, array elements), but rather references to the array object. For example, if you declare an int array, the reference variable you declared can be reassigned to any int array (of any size), but cannot be reassigned to anything that is not an int array, including an int value. Remember, all arrays are objects, so an int array reference cannot refer to an int primitive. The following code demonstrates legal and illegal assignments for primitive arrays: int[] splats; int[] dats = new int[4]; char[] letters = new char[5]; splats = dats; // OK, dats refers to an int array splats = letters; // NOT OK, letters refers to a char array It's tempting to assume that because a variable of type byte, short, or char can be explicitly promoted and assigned to an int, an array of any of those types could be assigned to an int array. You can't do that in Java, but it would be just like those cruel, heartless (but otherwise attractive) exam developers to put tricky array assignment questions in the exam. Arrays that hold object references, as opposed to primitives, aren't as restrictive. Just as you can put a Honda object in a Car array (because Honda extends Car), you can assign an array of type Honda to a Car array reference variable as follows: Car[] cars; Honda[] cuteCars = new Honda[5]; cars = cuteCars; // OK because Honda is a type of Car Beer[] beers = new Beer [99]; cars = beers; // NOT OK, Beer is not a type of Car Apply the IS-A test to help sort the legal from the illegal. Honda IS-A Car, so a Honda array can be assigned to a Car array. Beer IS-A Car is not true; Beer does not extend Car (plus it doesn't make sense, unless you've already had too much of it). The rules for array assignment apply to interfaces as well as classes. An array declared as an interface type can reference an array of any type that implements the interface. Remember, any object from a class implementing a particular interface will pass the IS-A (instanceof) test for that interface. For example, if Box implements Foldable, the following is legal: Foldable[] foldingThings; Box[] boxThings = new Box[3]; foldingThings = boxThings; // OK, Box implements Foldable, so Box IS-A Foldable Array Reference Assignments for Multidimensional Arrays When you assign an array to a previously declared array reference, the array you're assigning must be the same dimension as the reference you're assigning it to. For Initializing an Array (Exam Objective 1.3) 223 You cannot reverse the legal assignments. A Car array cannot be assigned to a Honda array. A Car is not necessarily a Honda, so if you’ve declared a Honda array, it might blow up if you assigned a Car array to the Honda reference variable. Think about it: a Car array could hold a reference to a Ferrari, so someone who thinks they have an array of Hondas could suddenly find themselves with a Ferrari. Remember that the IS-A test can be checked in code using the instanceof operator. 224 Chapter 3: Assignments example, a two-dimensional array of int arrays cannot be assigned to a regular int array reference, as follows: int[] blots; int[][] squeegees = new int[3][]; blots = squeegees; // NOT OK, squeegees is a // two-d array of int arrays int[] blocks = new int[6]; blots = blocks; // OK, blocks is an int array Pay particular attention to array assignments using different dimensions. You might, for example, be asked if it's legal to assign an int array to the first element in an array of int arrays, as follows: int[][] books = new int[3][]; int[] numbers = new int[6]; int aNumber = 7; books[0] = aNumber; // NO, expecting an int array not an int books[0] = numbers; // OK, numbers is an int array Figure 3-6 shows an example of legal and illegal assignments for references to an array. Initialization Blocks We've talked about two places in a class where you can put code that performs operations: methods and constructors. Initialization blocks are the third place in a Java program where operations can be performed. Initialization blocks run when the class is first loaded (a static initialization block) or when an instance is created (an instance initialization block). Let's look at an example: class SmallInit { static int x; int y; static { x = 7 ; } // static init block { y = 8; } // instance init block } As you can see, the syntax for initialization blocks is pretty terse. They don't have names, they can't take arguments, and they don't return anything. A static initialization block runs once, when the class is first loaded. An instance initialization block runs once every time a new instance is created. Remember when we talked about the order in which constructor code executed? Instance init block code runs right Initialization Blocks (Exam Objective 1.3) 225 Figure 3-6 Legal and illegal array assignments 226 Chapter 3: Assignments after the call to super() in a constructor, in other words, after all super-constructors have run. You can have many initialization blocks in a class. It is important to note that unlike methods or constructors, the order in which initialization blocks appear in a class matters. When it's time for initialization blocks to run, if a class has more than one, they will run in the order in which they appear in the class file…in other words, from the top down. Based on the rules we just discussed, can you determine the output of the following program? class Init { Init(int x) { System.out.println("1-arg const"); } Init() { System.out.println("no-arg const"); } static { System.out.println("1st static init"); } { System.out.println("1st instance init"); } { System.out.println("2nd instance init"); } static { System.out.println("2nd static init"); } public static void main(String [] args) { new Init(); new Init(7); } } To figure this out, remember these rules: n Init blocks execute in the order they appear. n Static init blocks run once, when the class is first loaded. n Instance init blocks run every time a class instance is created. n Instance init blocks run after the constructor's call to super(). With those rules in mind, the following output should make sense: 1st static init 2nd static init 1st instance init 2nd instance init no-arg const 1st instance init 2nd instance init 1-arg const As you can see, the instance init blocks each ran twice. Instance init blocks are often used as a place to put code that all the constructors in a class should share. That way, the code doesn't have to be duplicated across constructors. Finally, if you make a mistake in your static init block, the JVM can throw an ExceptionInIninitalizationError. Let's look at an example, class InitError { static int [] x = new int[4]; static { x[4] = 5; } // bad array index! public static void main(String [] args) { } } which produces something like: Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: 4 at InitError.<clinit>(InitError.java:3) CERTIFICATION OBJECTIVE Using Wrapper Classes and Boxing (Exam Objective 3.1) 3.1 Develop code that uses the primitive wrapper classes (such as Boolean, Character, Double, Integer, etc.), and/or autoboxing & unboxing. Discuss the differences between the String, StringBuilder, and StringBuffer classes. Using Wrapper Classes and Boxing (Exam Objective 3.1) 227 By convention, init blocks usually appear near the top of the class file, somewhere around the constructors. However, this is the SCJP exam we’re talking about. Don’t be surprised if you find an init block tucked in between a couple of methods, looking for all the world like a compiler error waiting to happen! 228 Chapter 3: Assignments The wrapper classes in the Java API serve two primary purposes: n To provide a mechanism to "wrap" primitive values in an object so that the primitives can be included in activities reserved for objects, like being added to Collections, or returned from a method with an object return value. Note: With Java 5's addition of autoboxing (and unboxing), which we'll get to in a few pages, many of the wrapping operations that programmers used to do manually are now handled automatically. n To provide an assortment of utility functions for primitives. Most of these functions are related to various conversions: converting primitives to and from String objects, and converting primitives and String objects to and from different bases (or radix), such as binary, octal, and hexadecimal. An Overview of the Wrapper Classes There is a wrapper class for every primitive in Java. For instance, the wrapper class for int is Integer, the class for float is Float, and so on. Remember that the primitive name is simply the lowercase name of the wrapper except for char, which maps to Character, and int, which maps to Integer. Table 3-2 lists the wrapper classes in the Java API. Primitive Wrapper Class Constructor Arguments boolean Boolean boolean or String byte Byte byte or String char Character char double Double double or String float Float float, double, or String int Integer int or String long Long long or String short Short short or String table 3-2 Wrapper Classes and Their Constructor Arguments Creating Wrapper Objects For the exam you need to understand the three most common approaches for creating wrapper objects. Some approaches take a String representation of a primitive as an argument. Those that take a String throw NumberFormatException if the String provided cannot be parsed into the appropriate primitive. For example "two" can't be parsed into "2". Wrapper objects are immutable. Once they have been given a value, that value cannot be changed. We'll talk more about wrapper immutability when we discuss boxing in a few pages. The Wrapper Constructors All of the wrapper classes except Character provide two constructors: one that takes a primitive of the type being constructed, and one that takes a String representation of the type being constructed—for example, Integer i1 = new Integer(42); Integer i2 = new Integer("42"); or Float f1 = new Float(3.14f); Float f2 = new Float("3.14f"); The Character class provides only one constructor, which takes a char as an argument—for example, Character c1 = new Character('c'); The constructors for the Boolean wrapper take either a boolean value true or false, or a case-insensitive String with the value "true" or "false". Until Java 5, a Boolean object couldn't be used as an expression in a boolean test—for instance, Boolean b = new Boolean("false"); if (b) // won't compile, using Java 1.4 or earlier As of Java 5, a Boolean object can be used in a boolean test, because the compiler will automatically "un-box" the Boolean to a boolean. We'll be focusing on Java 5's autoboxing capabilities in the very next section—so stay tuned! Creating Wrapper Objects (Exam Objective 3.1) 229 230 Chapter 3: Assignments The valueOf() Methods The two (well, usually two) static valueOf() methods provided in most of the wrapper classes give you another approach to creating wrapper objects. Both methods take a String representation of the appropriate type of primitive as their first argument, the second method (when provided) takes an additional argument, int radix, which indicates in what base (for example binary, octal, or hexadecimal) the first argument is represented—for example, Integer i2 = Integer.valueOf("101011", 2); // converts 101011 // to 43 and // assigns the value // 43 to the // Integer object i2 or Float f2 = Float.valueOf("3.14f"); // assigns 3.14 to the // Float object f2 Using Wrapper Conversion Utilities As we said earlier, a wrapper's second big function is converting stuff. The following methods are the most commonly used, and are the ones you're most likely to see on the test. xxxValue() When you need to convert the value of a wrapped numeric to a primitive, use one of the many xxxValue() methods. All of the methods in this family are noarg methods. As you can see by referring to Table 3-3, there are 36 xxxValue() methods. Each of the six numeric wrapper classes has six methods, so that any numeric wrapper can be converted to any primitive numeric type—for example, Integer i2 = new Integer(42); // make a new wrapper object byte b = i2.byteValue(); // convert i2's value to a byte // primitive short s = i2.shortValue(); // another of Integer's xxxValue // methods double d = i2.doubleValue(); // yet another of Integer's // xxxValue methods or Float f2 = new Float(3.14f); // make a new wrapper object short s = f2.shortValue(); // convert f2's value to a short // primitive System.out.println(s); // result is 3 (truncated, not // rounded) parseXxx() and valueOf() The six parseXxx() methods (one for each numeric wrapper type) are closely related to the valueOf() method that exists in all of the numeric wrapper classes. Both parseXxx() and valueOf() take a String as an argument, throw a NumberFormatException (a.k.a. NFE) if the String argument is not properly formed, and can convert String objects from different bases (radix), when the underlying primitive type is any of the four integer types. (See Table 3-3.) The difference between the two methods is n parseXxx() returns the named primitive. n valueOf() returns a newly created wrapped object of the type that invoked the method. Here are some examples of these methods in action: double d4 = Double.parseDouble("3.14"); // convert a String // to a primitive System.out.println("d4 = " + d4); // result is d4 = 3.14 Double d5 = Double.valueOf("3.14"); // create a Double obj System.out.println(d5 instanceof Double); // result is "true" The next examples involve using the radix argument (in this case binary): long L2 = Long.parseLong("101010", 2); // binary String to a // primitive System.out.println("L2 = " + L2); // result is: L2 = 42 Long L3 = Long.valueOf("101010", 2); // binary String to // Long object System.out.println("L3 value = " + L3); // result is: // L3 value = 42 Using Wrapper Conversion Utilities (Exam Objective 3.1) 231 232 Chapter 3: Assignments toString() Class Object, the alpha class, has a toString() method. Since we know that all other Java classes inherit from class Object, we also know that all other Java classes have a toString() method. The idea of the toString() method is to allow you to get some meaningful representation of a given object. For instance, if you have a Collection of various types of objects, you can loop through the Collection and print out some sort of meaningful representation of each object using the toString() method, which is guaranteed to be in every class. We'll talk more about toString() in the Collections chapter, but for now let's focus on how toString() relates to the wrapper classes which, as we know, are marked final. All of the wrapper classes have a no-arg, nonstatic, instance version of toString(). This method returns a String with the value of the primitive wrapped in the object—for instance, Double d = new Double("3.14"); System.out.println("d = "+ d.toString() ); // result is d = 3.14 All of the numeric wrapper classes provide an overloaded, static toString() method that takes a primitive numeric of the appropriate type (Double. toString() takes a double, Long.toString() takes a long, and so on) and, of course, returns a String: String d = Double.toString(3.14); // d = "3.14" Finally, Integer and Long provide a third toString() method. It's static, its first argument is the primitive, and its second argument is a radix. The radix tells the method to take the first argument, which is radix 10 (base 10) by default, and convert it to the radix provided, then return the result as a String—for instance, String s = "hex = "+ Long.toString(254,16); // s = "hex = fe" toXxxString() (Binary, Hexadecimal, Octal) The Integer and Long wrapper classes let you convert numbers in base 10 to other bases. These conversion methods, toXxxString(), take an int or long, and return a String representation of the converted number, for example, String s3 = Integer.toHexString(254); // convert 254 to hex System.out.println("254 is " + s3); // result: "254 is fe" String s4 = Long.toOctalString(254); // convert 254 to octal System.out.print("254(oct) ="+ s4); // result: "254(oct) =376" In summary, the essential method signatures for Wrapper conversion methods are primitive xxxValue() - to convert a Wrapper to a primitive primitive parseXxx(String) - to convert a String to a primitive Wrapper valueOf(String) - to convert a String to a Wrapper Studying Table 3-3 is the single best way to prepare for this section of the test. If you can keep the differences between xxxValue(), parseXxx(), and valueOf() straight, you should do well on this part of the exam. Using Wrapper Conversion Utilities (Exam Objective 3.1) 233 Method s = static n = NFE exception Boolean Byte Character Double Float Integer Long Short byteValue x x x x x x doubleValue x x x x x x floatValue x x x x x x intValue x x x x x x longValue x x x x x x shortValue x x x x x x parseXxx s,n x x x x x x parseXxx s,n (with radix) x x x x valueOf s,n x x x x x x x valueOf s,n (with radix) x x x x toString x x x x x x x x toString s (primitive) x x x x x x x x toString s (primitive, radix) x x table 3-3 Common Wrapper Conversion Methods 234 Chapter 3: Assignments Autoboxing New to Java 5 is a feature known variously as: autoboxing, auto-unboxing, boxing, and unboxing. We'll stick with the terms boxing and unboxing. Boxing and unboxing make using wrapper classes more convenient. In the old, pre-Java 5 days, if you wanted to make a wrapper, unwrap it, use it, and then rewrap it, you might do something like this: Integer y = new Integer(567); // make it int x = y.intValue(); // unwrap it x++; // use it y = new Integer(x); // re-wrap it System.out.println("y = " + i); // print it Now, with new and improved Java 5 you can say Integer y = new Integer(567); // make it y++; // unwrap it, increment it, // rewrap it System.out.println("y = " + i); // print it Both examples produce the output: y = 568 And yes, you read that correctly. The code appears to be using the post-increment operator on an object reference variable! But it's simply a convenience. Behind the scenes, the compiler does the unboxing and reassignment for you. Earlier we mentioned that wrapper objects are immutable... this example appears to contradict that statement. It sure looks like y's value changed from 567 to 568. What actually happened, is that a second wrapper object was created and its value was set to 568. If only we could access that first wrapper object, we could prove it... Let's try this: Integer y = 567; // make a wrapper Integer x = y; // assign a second ref // var to THE wrapper System.out.println(y==x); // verify that they refer // to the same object y++; // unwrap, use, "rewrap" System.out.println(x + " " + y); // print values System.out.println(y==x); // verify that they refer // to different objects Which produces the output: true 567 568 false So, under the covers, when the compiler got to the line i++; it had to substitute something like this: int x2 = y.intValue(); // unwrap it x2++; // use it y = new Integer(x2); // re-wrap it Just as we suspected, there's gotta be a call to new in there somewhere. Boxing, ==, and equals() We just used == to do a little exploration of wrappers. Let's take a more thorough look at how wrappers work with ==, !=, and equals(). We'll talk a lot more about the equals() method in later chapters. For now all we have to know is that the intention of the equals() method is to determine whether two instances of a given class are "meaningfully equivalent." This definition is intentionally subjective; it's up to the creator of the class to determine what "equivalent" means for objects of the class in question. The API developers decided that for all the wrapper classes, two objects are equal if they are of the same type and have the same value. It shouldn't be surprising that Integer i1 = 1000; Integer i2 = 1000; if(i1 != i2) System.out.println("different objects"); if(i1.equals(i2)) System.out.println("meaningfully equal"); Produces the output: different objects meaningfully equal Autoboxing (Exam Objective 3.1) 235 236 Chapter 3: Assignments It's just two wrapper objects that happen to have the same value. Because they have the same int value, the equals() method considers them to be "meaningfully equivalent", and therefore returns true. How about this one: Integer i3 = 10; Integer i4 = 10; if(i3 == i4) System.out.println("same object"); if(i3.equals(i4)) System.out.println("meaningfully equal"); This example produces the output: same object meaningfully equal Yikes! The equals() method seems to be working, but what happened with == and != ? Why is != telling us that i1 and i2 are different objects, when == is saying that i3 and i4 are the same object? In order to save memory, two instances of the following wrapper objects will always be == when their primitive values are the same: n Boolean n Byte n Character from \u0000 to \u007f (7f is 127 in decimal) n Short and Integer from -128 to 127 Where Boxing Can Be Used As we discussed earlier, it's very common to use wrappers in conjunction with collections. Any time you want your collection to hold objects and primitives, you'll want to use wrappers to make those primitives collection-compatible. The general rule is that boxing and unboxing work wherever you can normally use a primitive or a wrapped object. The following code demonstrates some legal ways to use boxing: class UseBoxing { public static void main(String [] args) { UseBoxing u = new UseBoxing(); u.go(5); } boolean go(Integer i) { // boxes the int it was passed Boolean ifSo = true; // boxes the literal Short s = 300; // boxes the primitive if(ifSo) { // unboxing System.out.println(++s); // unboxes, increments, reboxes } return !ifSo; // unboxes, returns the inverse } } CERTIFICATION OBJECTIVE Overloading (Exam Objectives 1.5 and 5.4) 1.5 Given a code example, determine if a method is correctly overriding or overloading another method, and identify legal return values (including covariant returns), for the method. 5.4 Given a scenario, develop code that declares and/or invokes overridden or overloaded methods... Overloading (Exam Objectives 1.5 and 5.4) 237 Remember, wrapper reference variables can be null. That means that you have to watch out for code that appears to be doing safe primitive operations, but that could throw a NullPointerException: class Boxing2 { static Integer x; public static void main(String [] args) { doStuff(x); } static void doStuff(int z) { int z2 = 5; System.out.println(z2 + z); } } This code compiles fine, but the JVM throws a NullPointerException when it attempts to invoke doStuff(x), because x doesn’t refer to an Integer object, so there’s no value to unbox. 238 Chapter 3: Assignments Overloading Made Hard—Method Matching Although we covered some rules for overloading methods in Chapter 2, in this chapter we've added some new tools to our Java toolkit. In this section we're going to take a look at three factors that can make overloading a little tricky: n Widening n Autoboxing n Var-args When a class has overloaded methods, one of the compiler's jobs is to determine which method to use whenever it finds an invocation for the overloaded method. Let's look at an example that doesn't use any new Java 5 features: class EasyOver { static void go(int x) { System.out.print("int "); } static void go(long x) { System.out.print("long "); } static void go(double x) { System.out.print("double "); } public static void main(String [] args) { byte b = 5; short s = 5; long l = 5; float f = 5.0f; go(b); go(s); go(l); go(f); } } Which produces the output: int int long double This probably isn't much of a surprise; the calls that use byte and the short arguments are implicitly widened to match the version of the go() method that takes an int. Of course, the call with the long uses the long version of go(), and finally, the call that uses a float is matched to the method that takes a double. In every case, when an exact match isn't found, the JVM uses the method with the smallest argument that is wider than the parameter. You can verify for yourself that if there is only one version of the go() method, and it takes a double, it will be used to match all four invocations of go(). Overloading with Boxing and Var-args Now let's take our last example, and add boxing into the mix: class AddBoxing { static void go(Integer x) { System.out.println("Integer"); } static void go(long x) { System.out.println("long"); } public static void main(String [] args) { int i = 5; go(i); // which go() will be invoked? } } As we've seen earlier, if the only version of the go() method was one that took an Integer, then Java 5's boxing capability would allow the invocation of go() to succeed. Likewise, if only the long version existed, the compiler would use it to handle the go() invocation. The question is, given that both methods exist, which one will be used? In other words, does the compiler think that widening a primitive parameter is more desirable than performing an autoboxing operation? The answer is that the compiler will choose widening over boxing, so the output will be long Java 5's designers decided that the most important rule should be that pre-existing code should function the way it used to, so since widening capability already existed, a method that is invoked via widening shouldn't lose out to a newly created method that relies on boxing. Based on that rule, try to predict the output of the following: class AddVarargs { static void go(int x, int y) { System.out.println("int,int");} static void go(byte... x) { System.out.println("byte... "); } public static void main(String[] args) { byte b = 5; go(b,b); // which go() will be invoked? } } Overloading (Exam Objectives 1.5 and 5.4) 239 240 Chapter 3: Assignments As you probably guessed, the output is int,int Because, once again, even though each invocation will require some sort of conversion, the compiler will choose the older style before it chooses the newer style, keeping existing code more robust. So far we've seen that n Widening beats boxing n Widening beats var-args At this point, inquiring minds want to know, does boxing beat var-args? class BoxOrVararg { static void go(Byte x, Byte y) { System.out.println("Byte, Byte"); } static void go(byte... x) { System.out.println("byte... "); } public static void main(String [] args) { byte b = 5; go(b,b); // which go() will be invoked? } } As it turns out, the output is Byte, Byte A good way to remember this rule is to notice that the var-args method is "looser" than the other method, in that it could handle invocations with any number of int parameters. A var-args method is more like a catch-all method, in terms of what invocations it can handle, and as we'll see in Chapter 5, it makes most sense for catch-all capabilities to be used as a last resort. Widening Reference Variables We've seen that it's legal to widen a primitive. Can you widen a reference variable, and if so, what would it mean? Let's think back to our favorite polymorphic assignment: Animal a = new Dog(); Along the same lines, an invocation might be: class Animal {static void eat() { } } class Dog3 extends Animal { public static void main(String[] args) { Dog3 d = new Dog3(); d.go(d); // is this legal ? } void go(Animal a) { } } No problem! The go() method needs an Animal, and Dog3 IS-A Animal. (Remember, the go() method thinks it's getting an Animal object, so it will only ask it to do Animal things, which of course anything that inherits from Animal can do.) So, in this case, the compiler widens the Dog3 reference to an Animal, and the invocation succeeds. The key point here is that reference widening depends on inheritance, in other words the IS-A test. Because of this, it's not legal to widen from one wrapper class to another, because the wrapper classes are peers to one another. For instance, it's NOT valid to say that Short IS-A Integer. Overloading (Exam Objectives 1.5 and 5.4) 241 It’s tempting to think that you might be able to widen an Integer wrapper to a Long wrapper, but the following will NOT compile: class Dog4 { public static void main(String [] args) { Dog4 d = new Dog4(); d.test(new Integer(5)); // can't widen an Integer // to a Long } void test(Long x) { } } Remember, none of the wrapper classes will widen from one to another! Bytes won’t widen to Shorts, Shorts won’t widen to Longs, etc. 242 Chapter 3: Assignments Overloading When Combining Widening and Boxing We've looked at the rules that apply when the compiler can match an invocation to a method by performing a single conversion. Now let's take a look at what happens when more than one conversion is required. In this case the compiler will have to widen and then autobox the parameter for a match to be made: class WidenAndBox { static void go(Long x) { System.out.println("Long"); } public static void main(String [] args) { byte b = 5; go(b); // must widen then box - illegal } } This is just too much for the compiler: WidenAndBox.java:6: go(java.lang.Long) in WidenAndBox cannot be applied to (byte) Strangely enough, it IS possible for the compiler to perform a boxing operation followed by a widening operation in order to match an invocation to a method. This one might blow your mind: class BoxAndWiden { static void go(Object o) { Byte b2 = (Byte) o; // ok - it's a Byte object System.out.println(b2); } public static void main(String [] args) { byte b = 5; go(b); // can this byte turn into an Object ? } } This compiles (!), and produces the output: 5 Wow! Here's what happened under the covers when the compiler, then the JVM, got to the line that invokes the go() method: 1. The byte b was boxed to a Byte. 2. The Byte reference was widened to an Object (since Byte extends Object). 3. The go() method got an Object reference that actually refers to a Byte object. 4. The go() method cast the Object reference back to a Byte reference (remember, there was never an object of type Object in this scenario, only an object of type Byte!). 5. The go() method printed the Byte's value. Why didn't the compiler try to use the box-then-widen logic when it tried to deal with the WidenAndBox class? Think about it…if it tried to box first, the byte would have been converted to a Byte. Now we're back to trying to widen a Byte to a Long, and of course, the IS-A test fails. Overloading in Combination with Var-args What happens when we attempt to combine var-args with either widening or boxing in a method-matching scenario? Let's take a look: class Vararg { static void wide_vararg(long... x) { System.out.println("long..."); } static void box_vararg(Integer... x) { System.out.println("Integer..."); } public static void main(String [] args) { int i = 5; wide_vararg(5,5); // needs to widen and use var-args box_vararg(5,5); // needs to box and use var-args } } This compiles and produces: long... Integer... Overloading (Exam Objectives 1.5 and 5.4) 243 244 Chapter 3: Assignments As we can see, you can successfully combine var-args with either widening or boxing. Here's a review of the rules for overloading methods using widening, boxing, and var-args: n Primitive widening uses the "smallest" method argument possible. n Used individually, boxing and var-args are compatible with overloading. n You CANNOT widen from one wrapper type to another. (IS-A fails.) n You CANNOT widen and then box. (An int can't become a Long.) n You can box and then widen. (An int can become an Object, via Integer.) n You can combine var-args with either widening or boxing. There are more tricky aspects to overloading, but other than a few rules concerning generics (which we'll cover in Chapter 7), this is all you'll need to know for the exam. Phew! CERTIFICATION OBJECTIVE Garbage Collection (Exam Objective 7.4) 7.4 Given a code example, recognize the point at which an object becomes eligible for garbage collection, and determine what is and is not guaranteed by the garbage collection system. Recognize the behaviors of System.gc and finalization. Overview of Memory Management and Garbage Collection This is the section you've been waiting for! It's finally time to dig into the wonderful world of memory management and garbage collection. Memory management is a crucial element in many types of applications. Consider a program that reads in large amounts of data, say from somewhere else on a network, and then writes that data into a database on a hard drive. A typical design would be to read the data into some sort of collection in memory, perform some operations on the data, and then write the data into the database. After the data is written into the database, the collection that stored the data temporarily must be emptied of old data or deleted and re-created before processing the next batch. This operation might be performed thousands of times, and in languages like C or C++ that do not offer automatic garbage collection, a small flaw in the logic that manually empties or deletes the collection data structures can allow small amounts of memory to be improperly reclaimed or lost. Forever. These small losses are called memory leaks, and over many thousands of iterations they can make enough memory inaccessible that programs will eventually crash. Creating code that performs manual memory management cleanly and thoroughly is a nontrivial and complex task, and while estimates vary, it is arguable that manual memory management can double the development effort for a complex program. Java's garbage collector provides an automatic solution to memory management. In most cases it frees you from having to add any memory management logic to your application. The downside to automatic garbage collection is that you can't completely control when it runs and when it doesn't. Overview of Java's Garbage Collector Let's look at what we mean when we talk about garbage collection in the land of Java. From the 30,000 ft. level, garbage collection is the phrase used to describe automatic memory management in Java. Whenever a software program executes (in Java, C, C++, Lisp, Ruby, and so on), it uses memory in several different ways. We're not going to get into Computer Science 101 here, but it's typical for memory to be used to create a stack, a heap, in Java's case constant pools, and method areas. The heap is that part of memory where Java objects live, and it's the one and only part of memory that is in any way involved in the garbage collection process. A heap is a heap is a heap. For the exam it's important to know that you can call it the heap, you can call it the garbage collectible heap, you can call it Johnson, but there is one and only one heap. So, all of garbage collection revolves around making sure that the heap has as much free space as possible. For the purpose of the exam, what this boils down to is deleting any objects that are no longer reachable by the Java program running. We'll talk more about what reachable means, but let's drill this point in. When the garbage collector runs, its purpose is to find and delete objects that cannot be reached. If you think of a Java program as being in a constant cycle of creating the objects it needs (which occupy space on the heap), and then discarding them when they're no longer needed, creating new objects, discarding them, and so on, the missing piece of the puzzle is the garbage collector. When it runs, it looks for those Overview of Java’s Garbage Collector (Exam Objective 7.4) 245 246 Chapter 3: Assignments discarded objects and deletes them from memory so that the cycle of using memory and releasing it can continue. Ah, the great circle of life. When Does the Garbage Collector Run? The garbage collector is under the control of the JVM. The JVM decides when to run the garbage collector. From within your Java program you can ask the JVM to run the garbage collector, but there are no guarantees, under any circumstances, that the JVM will comply. Left to its own devices, the JVM will typically run the garbage collector when it senses that memory is running low. Experience indicates that when your Java program makes a request for garbage collection, the JVM will usually grant your request in short order, but there are no guarantees. Just when you think you can count on it, the JVM will decide to ignore your request. How Does the Garbage Collector Work? You just can't be sure. You might hear that the garbage collector uses a mark and sweep algorithm, and for any given Java implementation that might be true, but the Java specification doesn't guarantee any particular implementation. You might hear that the garbage collector uses reference counting; once again maybe yes maybe no. The important concept to understand for the exam is when does an object become eligible for garbage collection? To answer this question fully, we have to jump ahead a little bit and talk about threads. (See Chapter 9 for the real scoop on threads.) In a nutshell, every Java program has from one to many threads. Each thread has its own little execution stack. Normally, you (the programmer) cause at least one thread to run in a Java program, the one with the main() method at the bottom of the stack. However, as you'll learn in excruciating detail in Chapter 9, there are many really cool reasons to launch additional threads from your initial thread. In addition to having its own little execution stack, each thread has its own lifecycle. For now, all we need to know is that threads can be alive or dead. With this background information, we can now say with stunning clarity and resolve that an object is eligible for garbage collection when no live thread can access it. (Note: Due to the vagaries of the String constant pool, the exam focuses its garbage collection questions on non- String objects, and so our garbage collection discussions apply to only non-String objects too.) Based on that definition, the garbage collector does some magical, unknown operations, and when it discovers an object that can't be reached by any live thread, it will consider that object as eligible for deletion, and it might even delete it at some point. (You guessed it; it also might not ever delete it.) When we talk about reaching an object, we're really talking about having a reachable reference variable that refers to the object in question. If our Java program has a reference variable that refers to an object, and that reference variable is available to a live thread, then that object is considered reachable. We'll talk more about how objects can become unreachable in the following section. Can a Java application run out of memory? Yes. The garbage collection system attempts to remove objects from memory when they are not used. However, if you maintain too many live objects (objects referenced from other live objects), the system can run out of memory. Garbage collection cannot ensure that there is enough memory, only that the memory that is available will be managed as efficiently as possible. Writing Code That Explicitly Makes Objects Eligible for Collection In the preceding section, we learned the theories behind Java garbage collection. In this section, we show how to make objects eligible for garbage collection using actual code. We also discuss how to attempt to force garbage collection if it is necessary, and how you can perform additional cleanup on objects before they are removed from memory. Nulling a Reference As we discussed earlier, an object becomes eligible for garbage collection when there are no more reachable references to it. Obviously, if there are no reachable references, it doesn't matter what happens to the object. For our purposes it is just floating in space, unused, inaccessible, and no longer needed. The first way to remove a reference to an object is to set the reference variable that refers to the object to null. Examine the following code: 1. public class GarbageTruck { 2. public static void main(String [] args) { 3. StringBuffer sb = new StringBuffer("hello"); 4. System.out.println(sb); 5. // The StringBuffer object is not eligible for collection 6. sb = null; 7. // Now the StringBuffer object is eligible for collection 8. } 9. } The StringBuffer object with the value hello is assigned to the reference variable sb in the third line. To make the object eligible (for GC), we set the reference variable sb to null, which removes the single reference that existed to the Writing Code That Explicitly Makes Objects Eligible for Collection (Exam Objective 7.4) 247 248 Chapter 3: Assignments StringBuffer object. Once line 6 has run, our happy little hello StringBuffer object is doomed, eligible for garbage collection. Reassigning a Reference Variable We can also decouple a reference variable from an object by setting the reference variable to refer to another object. Examine the following code: class GarbageTruck { public static void main(String [] args) { StringBuffer s1 = new StringBuffer("hello"); StringBuffer s2 = new StringBuffer("goodbye"); System.out.println(s1); // At this point the StringBuffer "hello" is not eligible s1 = s2; // Redirects s1 to refer to the "goodbye" object // Now the StringBuffer "hello" is eligible for collection } } Objects that are created in a method also need to be considered. When a method is invoked, any local variables created exist only for the duration of the method. Once the method has returned, the objects created in the method are eligible for garbage collection. There is an obvious exception, however. If an object is returned from the method, its reference might be assigned to a reference variable in the method that called it; hence, it will not be eligible for collection. Examine the following code: import java.util.Date; public class GarbageFactory { public static void main(String [] args) { Date d = getDate(); doComplicatedStuff(); System.out.println("d = " + d); } public static Date getDate() { Date d2 = new Date(); StringBuffer now = new StringBuffer(d2.toString()); System.out.println(now); return d2; } } In the preceding example, we created a method called getDate() that returns a Date object. This method creates two objects: a Date and a StringBuffer containing the date information. Since the method returns the Date object, it will not be eligible for collection even after the method has completed. The StringBuffer object, though, will be eligible, even though we didn't explicitly set the now variable to null. Isolating a Reference There is another way in which objects can become eligible for garbage collection, even if they still have valid references! We call this scenario "islands of isolation." A simple example is a class that has an instance variable that is a reference variable to another instance of the same class. Now imagine that two such instances exist and that they refer to each other. If all other references to these two objects are removed, then even though each object still has a valid reference, there will be no way for any live thread to access either object. When the garbage collector runs, it can usually discover any such islands of objects and remove them. As you can imagine, such islands can become quite large, theoretically containing hundreds of objects. Examine the following code: public class Island { Island i; public static void main(String [] args) { Island i2 = new Island(); Island i3 = new Island(); Island i4 = new Island(); i2.i = i3; // i2 refers to i3 i3.i = i4; // i3 refers to i4 i4.i = i2; // i4 refers to i2 i2 = null; i3 = null; i4 = null; // do complicated, memory intensive stuff } } When the code reaches // do complicated, the three Island objects (previously known as i2, i3, and i4) have instance variables so that they refer to Writing Code That Explicitly Makes Objects Eligible for Collection (Exam Objective 7.4) 249 250 Chapter 3: Assignments each other, but their links to the outside world (i2, i3, and i4) have been nulled. These three objects are eligible for garbage collection. This covers everything you will need to know about making objects eligible for garbage collection. Study Figure 3-7 to reinforce the concepts of objects without references and islands of isolation. Forcing Garbage Collection The first thing that should be mentioned here is that, contrary to this section's title, garbage collection cannot be forced. However, Java provides some methods that allow you to request that the JVM perform garbage collection. For example, if you are about to perform some time-sensitive operations, you probably want to minimize the chances of a delay caused by garbage collection. But you must remember that the methods that Java provides are requests, and not demands; the virtual machine will do its best to do what you ask, but there is no guarantee that it will comply. Figure 3-7 "Island" objects eligible for garbage collection In reality, it is possible only to suggest to the JVM that it perform garbage collection. However, there are no guarantees the JVM will actually remove all of the unused objects from memory (even if garbage collection is run). It is essential that you understand this concept for the exam. The garbage collection routines that Java provides are members of the Runtime class. The Runtime class is a special class that has a single object (a Singleton) for each main program. The Runtime object provides a mechanism for communicating directly with the virtual machine. To get the Runtime instance, you can use the method Runtime.getRuntime(), which returns the Singleton. Once you have the Singleton you can invoke the garbage collector using the gc() method. Alternatively, you can call the same method on the System class, which has static methods that can do the work of obtaining the Singleton for you. The simplest way to ask for garbage collection (remember—just a request) is System.gc(); Theoretically, after calling System.gc(), you will have as much free memory as possible. We say theoretically because this routine does not always work that way. First, your JVM may not have implemented this routine; the language specification allows this routine to do nothing at all. Second, another thread (again, see the Chapter 9) might grab lots of memory right after you run the garbage collector. This is not to say that System.gc() is a useless method—it's much better than nothing. You just can't rely on System.gc() to free up enough memory so that you don't have to worry about running out of memory. The Certification Exam is interested in guaranteed behavior, not probable behavior. Now that we are somewhat familiar with how this works, let's do a little experiment to see if we can see the effects of garbage collection. The following program lets us know how much total memory the JVM has available to it and how much free memory it has. It then creates 10,000 Date objects. After this, it tells us how much memory is left and then calls the garbage collector (which, if it decides to run, should halt the program until all unused objects are removed). The final free memory result should indicate whether it has run. Let's look at the program: 1. import java.util.Date; 2. public class CheckGC { 3. public static void main(String [] args) { 4. Runtime rt = Runtime.getRuntime(); 5. System.out.println("Total JVM memory: " + rt.totalMemory()); Writing Code That Explicitly Makes Objects Eligible for Collection (Exam Objective 7.4) 251 252 Chapter 3: Assignments 6. System.out.println("Before Memory = " + rt.freeMemory()); 7. Date d = null; 8. for(int i = 0;i<10000;i++) { 9. d = new Date(); 10. d = null; 11. } 12. System.out.println("After Memory = " + rt.freeMemory()); 13. rt.gc(); // an alternate to System.gc() 14. System.out.println("After GC Memory = " + rt.freeMemory()); 15. } 16. } Now, let's run the program and check the results: Total JVM memory: 1048568 Before Memory = 703008 After Memory = 458048 After GC Memory = 818272 As we can see, the JVM actually did decide to garbage collect (i.e., delete) the eligible objects. In the preceding example, we suggested to the JVM to perform garbage collection with 458,048 bytes of memory remaining, and it honored our request. This program has only one user thread running, so there was nothing else going on when we called rt.gc(). Keep in mind that the behavior when gc() is called may be different for different JVMs, so there is no guarantee that the unused objects will be removed from memory. About the only thing you can guarantee is that if you are running very low on memory, the garbage collector will run before it throws an OutOfMemoryException. Exercise 3-2 Try changing the CheckGC program by putting lines 13 and 14 inside a loop. You might see that not all memory is released on any given run of the GC. Cleaning Up Before Garbage Collection—the finalize() Method Java provides you a mechanism to run some code just before your object is deleted by the garbage collector. This code is located in a method named finalize() that all classes inherit from class Object. On the surface this sounds like a great idea; maybe your object opened up some resources, and you'd like to close them before your object is deleted. The problem is that, as you may have gathered by now, you can't count on the garbage collector to ever delete an object. So, any code that you put into your class's overridden finalize() method is not guaranteed to run. The finalize() method for any given object might run, but you can't count on it, so don't put any essential code into your finalize() method. In fact, we recommend that in general you don't override finalize() at all. Tricky Little finalize() Gotcha's There are a couple of concepts concerning finalize() that you need to remember. n For any given object, finalize() will be called only once (at most) by the garbage collector. n Calling finalize() can actually result in saving an object from deletion. Let's look into these statements a little further. First of all, remember that any code that you can put into a normal method you can put into finalize(). For example, in the finalize() method you could write code that passes a reference to the object in question back to another object, effectively uneligiblizing the object for garbage collection. If at some point later on this same object becomes eligible for garbage collection again, the garbage collector can still process this object and delete it. The garbage collector, however, will remember that, for this object, finalize() already ran, and it will not run finalize() again. Certification Summary This was a monster chapter! Don't worry if you find that you have to review some of these topics as you get into later chapters. This chapter has a lot of foundation stuff that will come into play later. We started the chapter by reviewing the stack and the heap; remember local variables live on the stack, and instance variables live with their objects on the heap. Certification Summary 253 254 Chapter 3: Assignments We reviewed legal literals for primitives and Strings, then we discussed the basics of assigning values to primitives and reference variables, and the rules for casting primitives. Next we discussed the concept of scope, or "How long will this variable live?" Remember the four basic scopes, in order of lessening lifespan: static, instance, local, block. We covered the implications of using uninitialized variables, and the importance of the fact that local variables MUST be assigned a value explicitly. We talked about some of the tricky aspects of assigning one reference variable to another, and some of the finer points of passing variables into methods, including a discussion of "shadowing." The next topic was creating arrays, where we talked about declaring, constructing, and initializing one-, and multi-dimensional arrays. We talked about anonymous arrays, and arrays of references. Next we reviewed static and instance initialization blocks, what they look like, and when they are called. Phew! We continued the chapter with a discussion of the wrapper classes; used to create immutable objects that hold a primitive, and also used to provide conversion capabilities for primitives: remember valueOf(), xxxValue(), and parseXxx(). Closely related to wrappers, we talked about a big new feature in Java 5, autoboxing. Boxing is a way to automate the use of wrappers, and we covered some of its trickier aspects such as how wrappers work with == and the equals() method. Having added boxing to our toolbox, it was time to take a closer look at method overloading and how boxing and var-args, in conjunction with widening conversions, make overloading more complicated. Finally, we dove into garbage collection, Java's automatic memory management feature. We learned that the heap is where objects live and where all the cool garbage collection activity takes place. We learned that in the end, the JVM will perform garbage collection whenever it wants to. You (the programmer) can request a garbage collection run, but you can't force it. We talked about garbage collection only applying to objects that are eligible, and that eligible means "inaccessible from any live thread." Finally, we discussed the rarely useful finalize() method, and what you'll have to know about it for the exam. All in all, one fascinating chapter. two -minute dril Stack and Heap q Local variables (method variables) live on the stack. q Objects and their instance variables live on the heap. Literals and Primitive Casting (Objective 1.3) q Integer literals can be decimal, octal (e.g. 013), or hexadecimal (e.g. 0x3d). q Literals for longs end in L or l. q Float literals end in F or f, double literals end in a digit or D or d. q The boolean literals are true and false. q Literals for chars are a single character inside single quotes: 'd'. Scope (Objectives 1.3 and 7.6) q Scope refers to the lifetime of a variable. q There are four basic scopes: q Static variables live basically as long as their class lives. q Instance variables live as long as their object lives. q Local variables live as long as their method is on the stack; however, if their method invokes another method, they are temporarily unavailable. q Block variables (e.g.., in a for or an if) live until the block completes. Basic Assignments (Objectives 1.3 and 7.6) q Literal integers are implicitly ints. q Integer expressions always result in an int-sized result, never smaller. q Floating-point numbers are implicitly doubles (64 bits). q Narrowing a primitive truncates the high order bits. q Compound assignments (e.g. +=), perform an automatic cast. q A reference variable holds the bits that are used to refer to an object. q Reference variables can refer to subclasses of the declared type but not to superclasses. Two-Minute Drill 255 3 256 Chapter 3: Assignments q When creating a new object, e.g., Button b = new Button();, three things happen: q Make a reference variable named b, of type Button q Create a new Button object q Assign the Button object to the reference variable b Using a Variable or Array Element That Is Uninitialized and Unassigned (Objectives 1.3 and 7.6) q When an array of objects is instantiated, objects within the array are not instantiated automatically, but all the references get the default value of null. q When an array of primitives is instantiated, elements get default values. q Instance variables are always initialized with a default value. q Local/automatic/method variables are never given a default value. If you attempt to use one before initializing it, you'll get a compiler error. Passing Variables into Methods (Objective 7.3) q Methods can take primitives and/or object references as arguments. q Method arguments are always copies. q Method arguments are never actual objects (they can be references to objects). q A primitive argument is an unattached copy of the original primitive. q A reference argument is another copy of a reference to the original object. q Shadowing occurs when two variables with different scopes share the same name. This leads to hard-to-find bugs, and hard-to-answer exam questions. Array Declaration, Construction, and Initialization (Obj. 1.3) q Arrays can hold primitives or objects, but the array itself is always an object. q When you declare an array, the brackets can be left or right of the name. q It is never legal to include the size of an array in the declaration. q You must include the size of an array when you construct it (using new) unless you are creating an anonymous array. q Elements in an array of objects are not automatically created, although primitive array elements are given default values. q You'll get a NullPointerException if you try to use an array element in an object array, if that element does not refer to a real object. q Arrays are indexed beginning with zero. q An ArrayIndexOutOfBoundsException occurs if you use a bad index value. q Arrays have a length variable whose value is the number of array elements. q The last index you can access is always one less than the length of the array. q Multidimensional arrays are just arrays of arrays. q The dimensions in a multidimensional array can have different lengths. q An array of primitives can accept any value that can be promoted implicitly to the array's declared type;. e.g., a byte variable can go in an int array. q An array of objects can hold any object that passes the IS-A (or instanceof) test for the declared type of the array. For example, if Horse extends Animal, then a Horse object can go into an Animal array. q If you assign an array to a previously declared array reference, the array you're assigning must be the same dimension as the reference you're assigning it to. q You can assign an array of one type to a previously declared array reference of one of its supertypes. For example, a Honda array can be assigned to an array declared as type Car (assuming Honda extends Car). Initialization Blocks (Objectives 1.3 and 7.6) q Static initialization blocks run once, when the class is first loaded. q Instance initialization blocks run every time a new instance is created. They run after all super-constructors and before the constructor's code has run. q If multiple init blocks exist in a class, they follow the rules stated above, AND they run in the order in which they appear in the source file. Using Wrappers (Objective 3.1) q The wrapper classes correlate to the primitive types. q Wrappers have two main functions: q To wrap primitives so that they can be handled like objects q To provide utility methods for primitives (usually conversions) q The three most important method families are q xxxValue() Takes no arguments, returns a primitive q parseXxx() Takes a String, returns a primitive, throws NFE q valueOf() Takes a String, returns a wrapped object, throws NFE Two-Minute Drill 257 258 Chapter 3: Assignments q Wrapper constructors can take a String or a primitive, except for Character, which can only take a char. q Radix refers to bases (typically) other than 10; octal is radix = 8, hex = 16. Boxing (Objective 3.1) q As of Java 5, boxing allows you to convert primitives to wrappers or to convert wrappers to primitives automatically. q Using == with wrappers is tricky; wrappers with the same small values (typically lower than 127), will be ==, larger values will not be ==. Advanced Overloading (Objectives 1.5 and 5.4) q Primitive widening uses the "smallest" method argument possible. q Used individually, boxing and var-args are compatible with overloading. q You CANNOT widen from one wrapper type to another. (IS-A fails.) q You CANNOT widen and then box. (An int can't become a Long.) q You can box and then widen. (An int can become an Object, via an Integer.) q You can combine var-args with either widening or boxing. Garbage Collection (Objective 7.4) q In Java, garbage collection (GC) provides automated memory management. q The purpose of GC is to delete objects that can't be reached. q Only the JVM decides when to run the GC, you can only suggest it. q You can't know the GC algorithm for sure. q Objects must be considered eligible before they can be garbage collected. q An object is eligible when no live thread can reach it. q To reach an object, you must have a live, reachable reference to that object. q Java applications can run out of memory. q Islands of objects can be GCed, even though they refer to each other. q Request garbage collection with System.gc(); (recommended). q Class Object has a finalize() method. q The finalize() method is guaranteed to run once and only once before the garbage collector deletes an object. q The garbage collector makes no guarantees, finalize() may never run. q You can uneligibilize an object for GC from within finalize(). Self Test 1. Given: class Scoop { static int thrower() throws Exception { return 42; } public static void main(String [] args) { try { int x = thrower(); } catch (Exception e) { x++; } finally { System.out.println("x = " + ++x); } } } What is the result? A. x = 42 B. x = 43 C. x = 44 D. Compilation fails. E. The code runs with no output. 2. Given: class CardBoard { Short story = 5; CardBoard go(CardBoard cb) { cb = null; return cb; } public static void main(String[] args) { CardBoard c1 = new CardBoard(); CardBoard c2 = new CardBoard(); CardBoard c3 = c1.go(c2); c1 = null; // do Stuff } } When // doStuff is reached, how many objects are eligible for GC? A. 0 B. 1 C. 2 D. Compilation fails. E. It is not possible to know. F. An exception is thrown at runtime. Self Test 259 3. Given: class Alien { String invade(short ships) { return "a few"; } String invade(short... ships) { return "many"; } } class Defender { public static void main(String [] args) { System.out.println(new Alien().invade(7)); } } What is the result? A. many B. a few C. Compilation fails. D. The output is not predictable. E. An exception is thrown at runtime. 4. Given: 1. class Dims { 2. public static void main(String[] args) { 3. int[][] a = {{1,2,}, {3,4}}; 4. int[] b = (int[]) a[1]; 5. Object o1 = a; 6. int[][] a2 = (int[][]) o1; 7. int[] b2 = (int[]) o1; 8. System.out.println(b[1]); 9. } 10. } What is the result? A. 2 B. 4 C. An exception is thrown at runtime. D. Compilation fails due to an error on line 4. E. Compilation fails due to an error on line 5. F. Compilation fails due to an error on line 6. G. Compilation fails due to an error on line 7. 5. Given: 260 Chapter 3: Assignments class Eggs { int doX(Long x, Long y) { return 1; } int doX(long... x) { return 2; } int doX(Integer x, Integer y) { return 3; } int doX(Number n, Number m) { return 4; } public static void main(String[] args) { new Eggs().go(); } void go() { short s = 7; System.out.print(doX(s,s) + " "); System.out.println(doX(7,7)); } } What is the result? A. 1 1 B. 2 1 C. 3 1 D. 4 1 E. 2 3 F. 3 3 G. 4 3 6. Given: class Mixer { Mixer() { } Mixer(Mixer m) { m1 = m; } Mixer m1; public static void main(String[] args) { Mixer m2 = new Mixer(); Mixer m3 = new Mixer(m2); m3.go(); Mixer m4 = m3.m1; m4.go(); Mixer m5 = m2.m1; m5.go(); } void go() { System.out.print("hi "); } } What is the result? A. hi B. hi hi C. hi hi hi D. Compilation fails E. hi, followed by an exception F. hi hi, followed by an exception Self Test 261 262 Chapter 3: Assignments 7. Given: 1. class Zippy { 2. String[] x; 3. int[] a [] = {{1,2}, {1}}; 4. Object c = new long[4]; 5. Object[] d = x; 6. } What is the result? A. Compilation succeeds. B. Compilation fails due only to an error on line 3. C. Compilation fails due only to an error on line 4. D. Compilation fails due only to an error on line 5. E. Compilation fails due to errors on lines 3 and 5. F. Compilation fails due to errors on lines 3, 4, and 5. 8. Given: class Fizz { int x = 5; public static void main(String[] args) { final Fizz f1 = new Fizz(); Fizz f2 = new Fizz(); Fizz f3 = FizzSwitch(f1,f2); System.out.println((f1 == f3) + " " + (f1.x == f3.x)); } static Fizz FizzSwitch(Fizz x, Fizz y) { final Fizz z = x; z.x = 6; return z; } } What is the result? A. true true B. false true C. true false D. false false E. Compilation fails. F. An exception is thrown at runtime. 9. Given: class Knowing { static final long tooth = 343L; static long doIt(long tooth) { System.out.print(++tooth + " "); return ++tooth; } public static void main(String[] args) { System.out.print(tooth + " "); final long tooth = 340L; new Knowing().doIt(tooth); System.out.println(tooth); } } What is the result? A. 343 340 340 B. 343 340 342 C. 343 341 342 D. 343 341 340 E. 343 341 343 F. Compilation fails. G. An exception is thrown at runtime. 10. Which is true? (Choose all that apply.) A. The invocation of an object’s finalize() method is always the last thing that happens before an object is garbage collected (GCed). B. When a stack variable goes out of scope it is eligible for GC. C. Some reference variables live on the stack, and some live on the heap. D. Only objects that have no reference variables referring to them can be eligible for GC. E. It’s possible to request the GC via methods in either java.lang.Runtime or java.lang.System classes. 11. Given: 1. class Convert { 2. public static void main(String[] args) { 3. Long xL = new Long(456L); 4. long x1 = Long.valueOf("123"); 5. Long x2 = Long.valueOf("123"); 6. long x3 = xL.longValue(); 7. Long x4 = xL.longValue(); 8. Long x5 = Long.parseLong("456"); 9. long x6 = Long.parseLong("123"); 10. } 11. } Self Test 263 264 Chapter 3: Assignments Which will compile using Java 5, but will NOT compile using Java 1.4? (Choose all that apply.) A. Line 4 B. Line 5 C. Line 6 D. Line 7 E. Line 8 F. Line 9 12. Given: 1. class Eco { 2. public static void main(String[] args) { 3. Eco e1 = new Eco(); 4. Eco e2 = new Eco(); 5. Eco e3 = new Eco(); 6. e3.e = e2; 7. e1.e = e3; 8. e2 = null; 9. e3 = null; 10. e2.e = e1; 11. e1 = null; 12. } 13. Eco e; 14. } At what point is only a single object eligible for GC? A. After line 8 runs. B. After line 9 runs. C. After line 10 runs. D. After line 11 runs. E. Compilation fails. F. Never in this program. G. An exception is thrown at runtime. 13. Given: 1. class Bigger { 2. public static void main(String[] args) { 3. // insert code here 4. } 5. } 6. class Better { 7. enum Faster {Higher, Longer}; 8. } Which, inserted independently at line 3, will compile? (Choose all that apply.) A. Faster f = Faster.Higher; B. Faster f = Better.Faster.Higher; C. Better.Faster f = Better.Faster.Higher; D. Bigger.Faster f = Bigger.Faster.Higher; E. Better.Faster f2; f2 = Better.Faster.Longer; F. Better b; b.Faster = f3; f3 = Better.Faster.Longer; 14. Given: class Bird { { System.out.print("b1 "); } public Bird() { System.out.print("b2 "); } } class Raptor extends Bird { static { System.out.print("r1 "); } public Raptor() { System.out.print("r2 "); } { System.out.print("r3 "); } static { System.out.print("r4 "); } } class Hawk extends Raptor { public static void main(String[] args) { System.out.print("pre "); new Hawk(); System.out.println("hawk "); } } What is the result? A. pre b1 b2 r3 r2 hawk B. pre b2 b1 r2 r3 hawk C. pre b2 b1 r2 r3 hawk r1 r4 D. r1 r4 pre b1 b2 r3 r2 hawk E. r1 r4 pre b2 b1 r2 r3 hawk F. pre r1 r4 b1 b2 r3 r2 hawk G. pre r1 r4 b2 b1 r2 r3 hawk H. The order of output cannot be predicted. I. Compilation fails. Self Test 265 SElf Test Answers 1. Given: class Scoop { static int thrower() throws Exception { return 42; } public static void main(String [] args) { try { int x = thrower(); } catch (Exception e) { x++; } finally { System.out.println("x = " + ++x); } } } What is the result? A. x = 42 B. x = 43 C. x = 44 D. Compilation fails. E. The code runs with no output. Answer: ® 3 D is correct, the variable x is only in scope within the try code block, it’s not in scope in the catch or finally blocks. (For the exam, get used to those horrible closing } } } .) ®˚ A, B, C, and E is are incorrect based on the above. (Objective 1.3) 2. Given: class CardBoard { Short story = 5; CardBoard go(CardBoard cb) { cb = null; return cb; } public static void main(String[] args) { CardBoard c1 = new CardBoard(); CardBoard c2 = new CardBoard(); CardBoard c3 = c1.go(c2); c1 = null; // do Stuff } } When // doStuff is reached, how many objects are eligible for GC? A. 0 B. 1 266 Chapter 3: Assignments C. 2 D. Compilation fails. E. It is not possible to know. F. An exception is thrown at runtime. Answer: ® 3 C is correct. Only one CardBoard object (c1) is eligible, but it has an associated Short wrapper object that is also eligible. ®˚ A, B, D, E, and F are incorrect based on the above. (Objective 7.4) 3. Given: class Alien { String invade(short ships) { return "a few"; } String invade(short... ships) { return "many"; } } class Defender { public static void main(String [] args) { System.out.println(new Alien().invade(7)); } } What is the result? A. many B. a few C. Compilation fails. D. The output is not predictable. E. An exception is thrown at runtime. Answer: ® 3 C is correct, compilation fails. The var-args declaration is fine, but invade takes a short, so the argument 7 needs to be cast to a short. With the cast, the answer is B, 'a few'. ®˚ A, B, D, and E are incorrect based on the above. (Objective 1.3) 4. Given: 1. class Dims { 2. public static void main(String[] args) { 3. int[][] a = {{1,2,}, {3,4}}; 4. int[] b = (int[]) a[1]; 5. Object o1 = a; 6. int[][] a2 = (int[][]) o1; 7. int[] b2 = (int[]) o1; 8. System.out.println(b[1]); 9. } } Self Test Answers 267 What is the result? A. 2 B. 4 C. An exception is thrown at runtime D. Compilation fails due to an error on line 4. E. Compilation fails due to an error on line 5. F. Compilation fails due to an error on line 6. G. Compilation fails due to an error on line 7. Answer: ® 3 C is correct. A ClassCastException is thrown at line 7 because o1 refers to an int[][] not an int[]. If line 7 was removed, the output would be 4. ®˚ A, B, D, E, F, and G are incorrect based on the above. (Objective 1.3) 5. Given: class Eggs { int doX(Long x, Long y) { return 1; } int doX(long... x) { return 2; } int doX(Integer x, Integer y) { return 3; } int doX(Number n, Number m) { return 4; } public static void main(String[] args) { new Eggs().go(); } void go() { short s = 7; System.out.print(doX(s,s) + " "); System.out.println(doX(7,7)); } } What is the result? A. 1 1 B. 2 1 C. 3 1 D. 4 1 E. 2 3 F. 3 3 G. 4 3 Answer: ® 3 G is correct. Two rules apply to the first invocation of doX(). You can’t widen and then box in one step, and var-args are always chosen last. Therefore you can’t widen shorts to either ints or longs, and then box them to Integers or Longs. But you can box shorts to Shorts and 268 Chapter 3: Assignments then widen them to Numbers, and this takes priority over using a var-args method. The second invocation uses a simple box from int to Integer. ®˚ A, B, C, D, E, and F are incorrect based on the above. (Objective 3.1) 6. Given: class Mixer { Mixer() { } Mixer(Mixer m) { m1 = m; } Mixer m1; public static void main(String[] args) { Mixer m2 = new Mixer(); Mixer m3 = new Mixer(m2); m3.go(); Mixer m4 = m3.m1; m4.go(); Mixer m5 = m2.m1; m5.go(); } void go() { System.out.print("hi "); } } What is the result? A. hi B. hi hi C. hi hi hi D. Compilation fails E. hi, followed by an exception F. hi hi, followed by an exception Answer: ® 3 F is correct. The m2 object’s m1 instance variable is never initialized, so when m5 tries to use it a NullPointerException is thrown. ®˚ A, B, C, D, and E are incorrect based on the above. (Objective 7.3) 7. Given: 1. class Zippy { 2. String[] x; 3. int[] a [] = {{1,2}, {1}}; 4. Object c = new long[4]; 5. Object[] d = x; 6. } What is the result? A. Compilation succeeds. B. Compilation fails due only to an error on line 3. C. Compilation fails due only to an error on line 4. Self Test Answers 269 270 Chapter 3: Assignments D. Compilation fails due only to an error on line 5. E. Compilation fails due to errors on lines 3 and 5. F. Compilation fails due to errors on lines 3, 4, and 5. Answer: ® 3 A is correct, all of these array declarations are legal. Lines 4 and 5 demonstrate that arrays can be cast. ®˚ B, C, D, E, and F are incorrect because this code compiles. (Objective 1.3) 8. Given: class Fizz { int x = 5; public static void main(String[] args) { final Fizz f1 = new Fizz(); Fizz f2 = new Fizz(); Fizz f3 = FizzSwitch(f1,f2); System.out.println((f1 == f3) + " " + (f1.x == f3.x)); } static Fizz FizzSwitch(Fizz x, Fizz y) { final Fizz z = x; z.x = 6; return z; } } What is the result? A. true true B. false true C. true false D. false false E. Compilation fails. F. An exception is thrown at runtime. Answer: ® 3 A is correct. The references f1, z, and f3 all refer to the same instance of Fizz. The final modifier assures that a reference variable cannot be referred to a different object, but final doesn’t keep the object’s state from changing. ®˚ B, C, D, E, and F are incorrect based on the above. (Objective 7.3) 9. Given: class Knowing { static final long tooth = 343L; static long doIt(long tooth) { System.out.print(++tooth + " "); return ++tooth; } public static void main(String[] args) { System.out.print(tooth + " "); final long tooth = 340L; new Knowing().doIt(tooth); System.out.println(tooth); } } What is the result? A. 343 340 340 B. 343 340 342 C. 343 341 342 D. 343 341 340 E. 343 341 343 F. Compilation fails. G. An exception is thrown at runtime. Answer: ® 3 D is correct. There are three different long variables named tooth. Remember that you can apply the final modifier to local variables, but in this case the 2 versions of tooth marked final are not changed. The only tooth whose value changes is the one not marked final. This program demonstrates a bad practice known as shadowing. ®˚ A, B, C, E, F, and G are incorrect based on the above. (Objective 7.3) 10. Which is true? (Choose all that apply.) A. The invocation of an object’s finalize() method is always the last thing that happens before an object is garbage collected (GCed). B. When a stack variable goes out of scope it is eligible for GC. C. Some reference variables live on the stack, and some live on the heap. D. Only objects that have no reference variables referring to them can be eligible for GC. E. It’s possible to request the GC via methods in either java.lang.Runtime or java.lang.System classes. Answer: ® 3 C and E are correct. When an object has a reference variable, the reference variable lives inside the object, on the heap. ®˚ A is incorrect, because if, the first time an object’s finalize() method runs, the object is saved from the GC, then the second time that object is about to be GCed, finalize() will not run. B is incorrect—stack variables are not dealt with by the GC. D is incorrect because objects can live in "islands of isolation" and be GC eligible. (Objective 7.4) Self Test Answers 271 272 Chapter 3: Assignments 11. Given: 1. class Convert { 2. public static void main(String[] args) { 3. Long xL = new Long(456L); 4. long x1 = Long.valueOf("123"); 5. Long x2 = Long.valueOf("123"); 6. long x3 = xL.longValue(); 7. Long x4 = xL.longValue(); 8. Long x5 = Long.parseLong("456"); 9. long x6 = Long.parseLong("123"); 10. } 11. } Which will compile using Java 5, but will NOT compile using Java 1.4? (Choose all that apply.) A. Line 4. B. Line 5. C. Line 6. D. Line 7. E. Line 8. F. Line 9. Answer: ® 3 A, D, and E are correct. Because of the methods’ return types, these method calls required autoboxing to compile. ®˚ B, C, and F are incorrect based on the above. (Objective 3.1) 12. Given: 1. class Eco { 2. public static void main(String[] args) { 3. Eco e1 = new Eco(); 4. Eco e2 = new Eco(); 5. Eco e3 = new Eco(); 6. e3.e = e2; 7. e1.e = e3; 8. e2 = null; 9. e3 = null; 10. e2.e = e1; 11. e1 = null; 12. } 13. Eco e; 14. } At what point is only a single object eligible for GC? A. After line 8 runs. B. After line 9 runs. C. After line 10 runs. D. After line 11 runs. E. Compilation fails. F. Never in this program. G. An exception is thrown at runtime. Answer: ® 3 G is correct. An error at line 10 causes a NullPointerException to be thrown because e2 was set to null in line 8. If line 10 was moved between lines 7 and 8, then F would be correct, because until the last reference is nulled none of the objects is eligible, and once the last reference is nulled, all three are eligible. ®˚ A, B, C, D, E, and F are incorrect based on the above. (Objective 7.4) 13. Given: 1. class Bigger { 2. public static void main(String[] args) { 3. // insert code here 4. } 5. } 6. class Better { 7. enum Faster {Higher, Longer}; 8. } Which, inserted independently at line 3, will compile? (Choose all that apply.) A. Faster f = Faster.Higher; B. Faster f = Better.Faster.Higher; C. Better.Faster f = Better.Faster.Higher; D. Bigger.Faster f = Bigger.Faster.Higher; E. Better.Faster f2; f2 = Better.Faster.Longer; F. Better b; b.Faster = f3; f3 = Better.Faster.Longer; Answer: ® 3 C and E are correct syntax for accessing an enum from another class. ®˚ A, B, D, and F are incorrect syntax. (Objective 1.3) Self Test Answers 273 274 Chapter 3: Assignments 14. Given: class Bird { { System.out.print("b1 "); } public Bird() { System.out.print("b2 "); } } class Raptor extends Bird { static { System.out.print("r1 "); } public Raptor() { System.out.print("r2 "); } { System.out.print("r3 "); } static { System.out.print("r4 "); } } class Hawk extends Raptor { public static void main(String[] args) { System.out.print("pre "); new Hawk(); System.out.println("hawk "); } } What is the result? A. pre b1 b2 r3 r2 hawk B. pre b2 b1 r2 r3 hawk C. pre b2 b1 r2 r3 hawk r1 r4 D. r1 r4 pre b1 b2 r3 r2 hawk E. r1 r4 pre b2 b1 r2 r3 hawk F. pre r1 r4 b1 b2 r3 r2 hawk G. pre r1 r4 b2 b1 r2 r3 hawk H. The order of output cannot be predicted. I. Compilation fails. Answer: ® 3 D is correct. Static init blocks are executed at class loading time, instance init blocks run right after the call to super() in a constructor. When multiple init blocks of a single type occur in a class, they run in order, from the top down. ®˚ A, B, C, E, F, G, H, and I are incorrect based on the above. Note: you’ll probably never see this many choices on the real exam! (Objective 1.3)