by Laura Lemay and Charles L. Perkins
Here at the start of Week 3, you've probably grasped the basics of the Java language from Week 1, and you've applied them fairly often to create applets in Week 2. You can stop here, if you like, and go on your merry way, knowing enough Java to get by.
Week 3 extends what you already know. In this week you'll learn more about advanced Java concepts such as access control and packages, and you'll learn techniques for structuring large programs in an efficient object-oriented way so your code can be more easily maintained and extended or, if you so choose, easily reused by other people.
Today we'll start with advanced Java language concepts for organizing and designing individual classes:
The techniques for programming you'll learn today involve different strategies and ways of thinking about how a class is organized. But the one thing all these techniques have in common is that they all use special modifier keywords in the Java language.
In Week 1 you learned how to define classes, methods, and variables in Java. Modifiers are keywords you add to those definitions to change their meaning. Classes, methods, and variables with modifiers are still classes, methods, and variables, but the modifiers change their behavior or how Java treats those elements.
Modifiers are special language keywords that modify the
definition (and the behavior) of a class, method, or variable.
Note |
You've already learned about a few of these modifiers earlier in the book, but here we'll talk about them in detail so you can get the bigger picture of why modifiers work the way they do. |
The Java language has a wide variety of modifiers, including
Some modifiers, as you can see, can apply only to classes and methods or only to methods and variables. For each of the modifiers, however, to use them you put them just previous to the class, method, or variable definition, as in the following examples:
public class MyApplet extends Java.applet.Applet { ... } private boolean engineState; static final double pi = 3.141559265 protected static final int MAXNUMELEMENTS = 128; public static void main(String args[]) { ...}
The order of modifiers is irrelevant to their meaning-your order can vary and is really a matter of taste. Pick a style and then be consistent with it throughout all your classes. Here is the usual order:
<access> static abstract synchronized volatile final native
In this definition, <access> can be public, protected, or private (but no more than one of them).
All the modifiers are essentially optional; none have to appear in a declaration. Good object-oriented programming style, however, suggests adding as many as are needed to best describe the intended use of, and restrictions on, the thing you're declaring. In some special situations (inside an interface, for example, as described tomorrow), certain modifiers are implicitly defined for you, and you needn't type them-they will be assumed to be there.
The most important modifiers in the language, from the standpoint of class and object design, are those that allow you to control the visibility of, and access to, variables and methods inside your classes.
Why would you care about controlling access to methods and variables inside your classes? If you remember way back to the beginning of this book, I used the analogy of the pc-how you can buy different pc components and put them all together so that they interact to create a larger system.
Each component in that pc system works in a particular way and has a specific way of interacting with the other components in the system. For example, a video card plugs into your motherboard using a standard socket and plug arrangement, as does your monitor to the back of the card. And then your computer can talk the right software language through the card to get bits up on the screen.
The video card itself has a whole lot of other internal features and capabilities beyond this basic hardware and software interface. But as a user or consumer of the card, I don't need to know what every single chip does, nor do I need to touch them in order to get the card to work. Given the standard interfaces, the card figures everything out and does what it needs to do internally. And, in fact, the manufacturer of the card most likely doesn't want me to go in and start mucking with individual chips or capabilities of the card, because I'm likely to screw something up. It's best if I just stick to the defined interface and let the internal workings stay hidden.
Classes and objects are the same way. While a class may define lots of methods and variables, not all of them are useful to a consumer of that class, and some may even be harmful if they're not used in the way they were intended to be used.
Access control is about controlling visibility. When a method or variable is visible to another class, its methods can reference (call, or modify) that method or variable. Protecting those methods and instance variables limits the visibility and the use of those methods and variables (and also limits what you have to document!). As a designer of a class or an entire hierarchy of classes, therefore, it's a good idea to define what the external appearance of a class is going to be, which variables and methods will be accessible for other users of that class, and which ones are for internal use only. This is called encapsulation and is an important feature of object-oriented design.
Encapsulation is the process of hiding the internal parts of an object's implementation and allowing access to that object only through a defined interface.
You may note that up to this point we haven't done very much of this in any of the examples; in fact, just about every variable and method we've created has been fairly promiscuous and had no access control whatsoever. The reason I approached the problem in this way is that it makes for simpler examples. As you become a more sophisticated programmer and create Java programs with lots of interrelated classes, you'll find that adding features such as encapsulation and protecting access to the internal workings of your classes makes for better-designed programs overall.
The Java language provides four levels of protection for methods
and instance variables: public,
private, protected,
and package (actually, the
latter isn't an explicit form of Java protection, but I've included
it here because it's nicely alliterative). Before applying protection
levels to your own code, you should know what each form means
and understand the fundamental relationships that a method or
variable within a class can have to the other classes in the system.
Note |
You can also protect entire classes using these modifiers. But class protection applies better once you know what packages are, so we'll postpone talking about that until tomorrow. |
The first form of protection we'll talk about is the one you've been unconsciously using all this time: what's called package protection. In C, there's the notion of hiding a name so that only the functions within a given source file can see it. Java doesn't have this kind of control; names will be happily found in other source files as long as Java knows where to find them. Instead of file-level protection, Java has the concept of packages, which, as you learned on Day 2, "Object-Oriented Programming and Java," and will learn a whole lot more about tomorrow, are a group of classes related by purpose or function.
Methods and variables with package protection are visible to all other classes in the same package, but not outside that package. This is the kind of protection you've been using up to this point, and it's not much protection at all. Much of the time you'll want to be more explicit when you define the protection for that class's methods and variables.
Package protection, the default level of protection, means that your methods and variables are accessible to all the other classes in the same package.
Package protection isn't an explicit modifier you can add to your
method or variable definitions; instead, it's the default protection
you get when you don't add any protection modifiers to those definitions.
Note |
You may not think you've been using packages at all up to this point, but actually, you have. In Java, if you don't explicitly put a class into a package, it'll be included in a default package that also includes all the other classes that aren't in a specific package. While not defining a class to be in a package works for simple examples, it's better if you just create packages instead. |
From the default protection you get with package protection, you can either become more restrictive or more loose in how you control the visibility and access to your methods and variables. The most restrictive form of protection is private, which limits the visibility of methods and instance variables to the class in which they're defined. A private instance variable, for example, can be used by methods inside the same class, but cannot be seen or used by any other class or object. Private methods, analogously, can be called by other methods inside that same class, but not by any other classes. In addition, neither private variables nor private methods are inherited by subclasses.
Private protection means that your methods and variables are accessible only to other methods in the same class.
To create a private method or instance variable, add the private modifier to its definition:
class Writer { private boolean writersBlock = true; private String mood; private int income = 0; private void getIdea(Inspiration in) { . . . } Book createBook(int numDays, long numPages) { ... } }
In this code example, the internal data to the class Writer (the variables writersBlock, mood, and income and the method getIdea()) is all private. The only method accessible from outside the Writer class is the createBook() method. createBook() is the only thing other objects (editor objects, perhaps?) can ask the Writer object to do; the other bits of data are implementation details that may affect how the book is written, but don't otherwise need to be visible or accessible from other sources.
The rule of thumb for private protection is that any data or behavior internal to the class that other classes or subclasses should not be touching should be private. Judicious use of private variables and methods is how you limit the functionality of a class to only those features you want visible outside that class-as with the example of the pc components. Remember that an object's primary job is to encapsulate its data-to hide it from the world's sight and limit its manipulation. Encapsulation separates design from implementation, minimizes the amount of information one class needs to know about another to get its job done, and reduces the extent of the code changes you need to make if your internal implementation changes. Also, by separating the public interface from the private implementation, your class's interface becomes more abstract-that is, more general purpose and more easily used for other purposes. Subclasses of your class can override the more abstract behavior of your public interface with their own private implementations.
In addition to picking and choosing which methods you'll want to keep private and which will be accessible to others, a general rule of thumb is that all the instance variables in a class should be private, and you should create special nonprivate methods to get or change those variables. You'll learn more about this rule and why it's important a little later, in the section "Instance Variable Protection and Accessor Methods."
The diametric opposite of private protection, and the least restrictive form of protection, is public. A method or variable that is declared with the public modifier is accessible to the class in which it's defined, all the subclasses of that class, all the classes in the package, and any other classes outside that package, anywhere in the entire universe of Java classes.
Public protection means that your methods and variables are accessible to other methods anywhere inside or outside the current class or package.
Indicating that a method or variable is public isn't necessarily a bad thing. Just as hiding the data that is internal to your class using private helps encapsulate an object, using public methods defines precisely what the interface to instances of your class is. If you expect your classes to be reused by other programmers in other programs, the methods that they'll be using to use your class should be public.
In many ways, public protection is very similar to the default package protection. Both allow methods and variables to be accessed by other classes in the same package. The difference occurs when you create packages of classes. Variables and methods with package protection can be used in classes that exist in the same package. But if someone imports your class into his own program from outside your package, those methods and variables will not be accessible unless they have been declared public. Once again, you'll learn more about packages tomorrow.
Public declarations work just like private ones; simply substitute the word public for private.
The final form of protection available in Java concerns the relationship between a class and its present and future subclasses declared inside or outside a package. These subclasses are much closer to a particular class than to any other "outside" classes for the following reasons:
To support a special level of visibility reserved for subclasses somewhat less restrictive than private, Java has an intermediate level of access between package and private called, appropriately, protected. Protected methods and variables are accessible to any class inside the package, as they would be if they were package protected, but those methods and variables are also available to any subclasses of your class that have been defined outside your package.
Protected protection means that your methods and variables
are accessible to all classes inside the package, but only to
subclasses outside the package.
Technical Note |
In C++, the protected modifier means that only subclasses can access a method or variable, period. Java's meaning of protected is slightly different, also allowing any class inside the package to access those methods and variables. |
Why would you need to do this? You may have methods in your class that are specific to its internal implementation-that is, not intended to be used by the general public-but that would be useful to subclasses for their own internal implementations. In this case, the developer of the subclass-be it you or someone else-can be trusted to be able to handle calling or overriding that method.
For example, let's say you had a class called AudioPlayer, which plays a digital audio file. AudioPlayer has a method called openSpeaker(), which is an internal method that interacts with the hardware to prepare the speaker for playing. openSpeaker() isn't important to anyone outside the AudioPlayer class, so at first glance you might want to make it private. A snippet of AudioPlayer might look something like this:
class AudioPlayer { private boolean openSpeaker(Speaker sp_ { // implementation details } }
This works fine if AudioPlayer isn't going to be subclassed. But
what if you were going to create a class called StereoAudioPlayer
that is a subclass of AudioPlayer?
This class would want access to the openSpeaker()
method so that it can override it and provide stereo-specific
speaker initialization. You still don't want the method generally
available to random objects (and so it shouldn't be public), but
you want the subclass to have access to it-so protected is just
the solution.
Technical Note |
In versions of Java and the JDK up to 1.0.1, you could use private and protected together to create yet another form of protection that would restrict access to methods or variables solely to subclasses of a given class. As of 1.0.2, this capability has been removed from the language. |
The differences between the various protection types can become
very confusing, particularly in the case of protected methods
and variables. Table 15.1, which summarizes exactly what is allowed
where, will help clarify the differences from the least restrictive
(public) to the most restrictive (private) forms of protection.
Visibility | ||||
From the same class | ||||
From any class in the same package | ||||
From any class outside the package | ||||
From a subclass in the same package | ||||
From a subclass outside the same package |
Setting up protections in new classes with new methods is easy; you make your decisions based on your design and apply the right modifiers. When you create subclasses and override other methods, however, you have to take into account the protection of the original method.
The general rule in Java is that you cannot override a method and make the new method more private than the original method (you can, however, make it more public). More specifically, the following rules for inherited methods are enforced by Java:
A good rule of thumb in object-oriented programming is that unless an instance variable is constant it should almost certainly be private. But, I hear you say, if instance variables are private, how can they be changed from outside the class? They can't. That's precisely the point. Instead, if you create special methods that indirectly read or change the value of that instance variable, you can much better control the interface of your classes and how those classes behave. You'll learn about how to do this later in this section.
In most cases, having someone else accessing or changing instance variables inside your object isn't a good idea. Take, for example, a class called circle, whose partial definition looks like this:
class Circle { int x, y, radius; Circle(int x, int y, int radius) { ... } void draw() { ... } }
The Circle class has three instance variables: for the x and y position of the center point, and of the radius. A constructor builds the circle from those three values, and the draw() method draws the circle on the screen. So far, so good, right?
So let's say you have a Circle object created and drawn on the screen. Then some other object comes along and changes the value of radius. Now what? Your circle doesn't know that the radius has changed. It doesn't know to redraw itself to take advantage of the new size of the circle. Changing the value of an instance variable doesn't in itself trigger any methods. You have to rely on the same random object that changed the radius to also call the draw() method. And that overly complicates the interface of your class, making it more prone to errors.
Another example of why it's better not to make instance variables publicly accessible is that it's not possible to prevent a nonconstant instance variable from being changed. In other words, you could create a variable that you'd intended to be read-only, and perhaps your program was well mannered and didn't go about changing that variable randomly-but because the variable is there and available someone else may very well change it without understanding your methodology.
If all your instance variables are private, how do you give access to them to the outside world? The answer is to write special methods to read and change that variable (one for reading the value of the variable, one for changing it) rather than allowing it to be read and changed directly. These methods are sometimes called accessor methods, mutator methods (for changing the variable) or simply getters and setters.
Accessor methods are special methods you implement to indirectly modify otherwise private instance variables.
Having a method to change a given instance variable means you can control both the value that variable is set to (to make sure it's within the boundaries you expect), as well as perform any other operations that may need to be done if that variable changes, for example, to redraw the circle.
Having two methods for reading and changing the variable also allows you to set up different protections for each. The method to read the value, for example, could be public, whereas the method to change the value can be private or protected, effectively creating a variable that's read-only except in a few cases (which is different from constant, which is read-only in all cases).
Using methods to access an instance variable is one of the most frequently used idioms in object-oriented programs. Applying it liberally throughout all your classes repays you numerous times with more robust and reusable programs.
Creating accessor methods for your instance variables simply involves creating two extra methods for each variable. There's nothing special about accessor methods; they're just like any other method. So, for example, here's a modified Circle class that has three private instance variables: x, y, and radius. The public getRadius() method is used to retrieve the value of the radius variable, and the setRadius() method is used to set it (and update other parts of the class that need to be updated at the same time):
class Circle { private int x, y radius; public int getRadius() { return radius; } public int setRadius(int value) { radius = value; draw(); doOtherStuff(); return radius; } .... }
In this modified example of the Circle class the accessor methods for the instance variable radius have the words set and get appended with the name of the variable. This is a naming convention popular among many programmers for accessor methods, so you always know which methods do what and to which variable. To access or change the value of the instance variable, therefore, you'd just call the methods setRadius() and getRadius(), respectively:
theCircle.getRadius(); //get the value theCircle.setRadius(4); //set the value (and redraw, etc)
Another convention for naming accessor methods is to use the same name for the methods as for the variable itself. In Java it is legal for instance variables and methods to have the same name; Java knows from how they are used to perform the right operation. While this does make accessor methods shorter to type (no extra "set" or "get" to type at the beginning of each variable), there are two problems with using this convention:
Which convention you use is a question of personal taste. The most important thing is to choose a convention and stick with it throughout all your classes so that your interfaces are consistent and understandable.
The idea behind declaring instance variables private and creating accessor methods is so that external users of your class will be forced to use the methods you choose to modify your class's data. But the benefit of accessor methods isn't just for use by objects external to yours; they're also there for you. Just because you have access to the actual instance variable inside your own class doesn't mean you can avoid using accessor methods.
Consider that one of the good reasons to make instance variables private is to hide implementation details from outside your object. Protecting a variable with accessor methods means that other objects don't need to know about anything other than the accessor methods-you can happily change the internal implementation of your class without wreaking havoc on everyone who's used your class. The same is true of your code inside that class; by keeping variables separate from accessors, if you must change something about a given instance variable all you have to change are the accessor methods and not every single reference to the variable itself. In terms of code maintenance and reuse, what's good for the goose (external users of your class) is generally also good for the gander (you, as a user of your own class).
You learned about class variables and methods early last week, so I won't repeat a long description of them here. Because they use modifiers, however, they deserve a cursory mention.
To create a class variable or method, simply include the word static in front of the method name. The static modifier typically comes after any protection modifiers, like this:
public class Circle { public static float pi = 3.14159265F; public float area(float r) { return pi * r * r; } }
Note |
The word static comes from C and C++. While static has a specific meaning for where a method or variable is stored in a program's runtime memory in those languages, static simply means that it's stored in the class in Java. Whenever you see the word static, remember to mentally substitute the word class. |
Both class variables and methods can be accessed using standard dot notation with either the class name or an object on the left side of the dot. However, the convention is to always use the name of the class, to clarify that a class variable is being used, and to help the reader to know instantly that the variable is global to all instances. Here are a few examples:
float circumference = 2 * Circle.pi * getRadius(); float randomNumer = Math.random();
Tip |
Class variables, for the same reasons as instance variables, can also benefit from being declared private and having accessor methods get or set their values. |
Listing 15.1 shows a class called CountInstances that uses class and instance variables to keep track of how many instances of that class have been created.
Listing 15.1. The CountInstances class, which uses class and instance variables.
1: public class CountInstances { 2: private static int numInstances = 0; 3: 4: protected static int getNumInstances() { 5: return numInstances; 6: } 7: 8: private static void addInstance() { 9: numInstances++; 10: } 11: 12: CountInstances() { 13: CountInstances.addInstance(); 14: } 15: 16: public static void main(String args[]) { 17: System.out.println("Starting with " + 18: CountInstances.getNumInstances() + " instances"); 19: for (int i = 0; i < 10; ++i) 20: new CountInstances(); 21: System.out.println("Created " + 22: CountInstances.getNumInstances() + " instances"); 23: } 24:}
Started with 0 instances Creates 10 instances
This example has a number of features, so let's go through it line by line. In line 2 we declare a private class variable to hold the number of instances (called numInstances). This is a class variable (declared static) because the number of instances is relevant to the class as a whole, not to any one instance. And it's private so that it follows the same rules as instance variables accessor methods.
Note the initialization of numInstances to 0 in that same line. Just as an instance variable is initialized when its instance is created, a class variable is initialized when its class is created. This class initialization happens essentially before anything else can happen to that class, or its instances, so the class in the example will work as planned.
In lines 4 through 6, we created a get method for that private instance variable to get its value (getNumInstances()). This method is also declared as a class method, as it applies directly to the class variable. The getNumInstances() method is declared protected, as opposed to public, because only this class and perhaps subclasses will be interested in that value; other random classes are therefore restricted from seeing it.
Note that there's no accessor method to set the value. The reason is that the value of the variable should be incremented only when a new instance is created; it should not be set to any random value. Instead of creating an accessor method, therefore, we'll create a special private method called addInstance() in lines 8 through 10 that increments the value of numInstances by 1.
Lines 12 through 14 have the constructor method for this class. Remember, constructors are called when a new object is created, which makes this the most logical place to call addInstance() and to increment the variable.
And finally, the main() method indicates that we can run this as a Java application and test all the other methods. In the main() method we create 10 instances of the CountInstances class, reporting after we're done the value of the numInstances class variable (which, predictably, prints 10).
Although it's not the final modifier I'll discuss today, the final modifier is used to finalize classes, methods, and variables. Finalizing a thing effectively "freezes" the implementation or value of that thing. More specifically, here's how final works with classes, variables, and methods:
Finalization (using the final modifier) freezes the implementation of a class, method, or variable.
To finalize a class, add the final modifier to its definition. final typically goes after any protection modifiers such as private or public:
public final class AFinalClass { . . . }
You declare a class final for only two reasons:
The Java class library uses final classes extensively. Classes that have been finalized to prevent their being subclassed include java.lang.System, java.net.InetAddress, and java.net.Socket (although, as you learned on Day 14, "Windows, Networking, and Other Tidbits," the latter will no longer be final as of Java 1.1). A good example of a class being declared final for efficiency reasons is java.lang.String. Strings are so common in Java, and so central to it that Java handles them specially.
In most cases, it will be a rare event for you to create a final class yourself since extendible classes are so much more useful than finalized classes, and the efficiency gains are minimal. You will, however, most likely have plenty of opportunity to be upset at certain system classes being final (making it more difficult to extend them).
A finalized variable means its value cannot be changed. This is effectively a constant, which you learned about early in Week 1. To declare constants in Java, use final variables with initial values:
public class AnotherFinalClass { public static final int aConstantInt = 123; public final String aConstantString = "Hello world!"; }
Local variables (those inside blocks of code surrounded by braces, for example, in while or for loops) can't be declared final.
Finalized methods are methods that cannot be overridden; that is, their implementations are frozen and cannot be redefined in subclasses.
public class ClassWithFinalMethod { public final void noOneGetsToDoThisButMe() { . . . } }
The only reason to declare a method final is efficiency. Normally, method signatures and implementations are matched up when your Java program runs, not when it's compiled. Remember that when you call a method, Java dynamically checks the current class and each superclass in turn for that method's definition. Although this makes methods very flexible to define and use, it's not very fast.
If you declare a method final, however, the compiler can then "in-line" it (stick its definition) right in the middle of methods that call it because it "knows" that no one else can ever subclass and override the method to change its meaning. Although you might not use final right away when writing a class, as you tune the system later, you may discover that a few methods have to be final to make your class fast enough. Almost all your methods will be fine, however, just as they are.
If you use accessor methods a lot (as recommended), changing your accessor methods to be final can be a quick way of speeding up your class. Because subclasses will rarely want to change the definitions of those accessor methods, there's little reason those methods should not be final.
The Java class library declares a lot of commonly used methods
final so that you'll benefit
from the speed-up. In the case of classes that are already final,
this makes perfect sense and is a wise choice. The few final
methods declared in non-final
classes will annoy you-your subclasses can no longer override
them. When efficiency becomes less of an issue for the Java environment,
many of these final methods
can be "unfrozen" again, restoring this lost flexibility
to the system.
Note |
Private methods are effectively final, as are all methods declared in a final class. Marking these latter methods final (as the Java library sometimes does) is legal, but redundant; the compiler already treats them as final. It's possible to use final methods for some of the same security reasons you use final classes, but it's a much rarer event. |
Whenever you arrange classes into an inheritance hierarchy, the presumption is that "higher" classes are more abstract and general, whereas "lower" subclasses are more concrete and specific. Often, as you design hierarchies of classes, you factor out common design and implementation into a shared superclass. That superclass won't have any instances; its sole reason for existing is to act as a common, shared repository for information that its subclasses use. These kinds of classes are called abstract classes, and you declare them using the abstract modifier. For example, the following skeleton class definition for the Fruit class declared that class to be both public and abstract:
public abstract class Fruit { ... }
Abstract classes can never be instantiated (you'll get a compiler error if you try), but they can contain anything a normal class can contain, including class and instance variables and methods with any kind of protection or finalization modifiers. In addition, abstract classes can also contain abstract methods. An abstract method is a method signature with no implementation; subclasses of the abstract class are expected to provide the implementation for that method. Abstract methods, in this way, provide the same basic concept as abstract classes; they're a way of factoring common behavior into superclasses and then providing specific concrete uses of those behaviors in subclasses.
Abstract classes are classes whose sole purpose is to provide common information for subclasses. Abstract classes can have no instances.
Abstract methods are methods with signatures, but no implementation. Subclasses of the class which contains that abstract method must provide its actual implementation.
Like abstract classes, abstract methods give you the ability to factor common information into a general superclass and then reuse that class in different ways.
The opposite of abstract is concrete: Concrete classes are classes that can be instantiated; concrete methods are those that have actual implementations.
Abstract methods are declared with the abstract modifier, which usually goes after the protection modifiers but before either static or final. In addition, they have no body. Abstract methods can only exist inside abstract classes; even if you have a class full of concrete methods, with only one abstract method, the whole class must be abstract. This is because abstract methods cannot be called; they have no implementation, so calling them would produce an error. Rather than worry about special-case abstract methods inside otherwise concrete instances, it's easier just to insist that abstract methods be contained only inside abstract classes.
Listing 15.2 shows two simple classes. One, appropriately called MyFirstAbstractClass, has an instance variable and two methods. One of those methods, subclassesImplementMe(), is abstract. The other, doSomething(), is concrete and has a normal definition.
The second class is AConcreteSubclass,
which is a subclass of MyFirstAbstractClass.
It provides the implementation of subclassesImplementMe(),
and inherits the remaining behavior from MyFirstAbstractClass.
Note |
Because both these classes are public, they must be defined in separate source files. |
Listing 15.2. Two classes: one abstract, one concrete.
1:ipublic abstract class MyFirstAbstractClass { 2: int anInstanceVariable; 3:p 4: public abstract int subclassesImplementMe(); // note no definition 5: 6: public void doSomething() { 7: . . . // a normal method 8: } 9:} 10: 11:public class AConcreteSubClass extends MyFirstAbstractClass { 12: public int subclassesImplementMe() { 13: . . . // we *must* implement this method here 14: } 15:}
Here are some attempted uses of these classes:
Object a = new MyFirstAbstractClass(); // illegal, is abstract Object c = new AConcreteSubClass(); // OK, a concrete subclass
Using an abstract class with nothing but abstract methods-that is, one that provides nothing but a template for behavior-is better accomplished in Java by using an interface (discussed tomorrow). Whenever a design calls for an abstraction that includes instance state and/or a partial implementation, however, an abstract class is your only choice.
Today you have learned how variables and methods can control their visibility and access by other classes via the four Ps of protection: public, package, protected, and private. You have also learned that although instance variables are most often declared private, declaring accessor methods allows you to control the reading and writing of them separately. Protection levels allow you, for example, to separate cleanly your public abstractions from their concrete representations.
You have also learned how to create class variables and methods, which are associated with the class itself, and how to declare final variables, methods, and classes to represent constants and fast or secure methods and classes.
Finally, you have discovered how to declare and use abstract classes, which cannot be instantiated, and abstract methods, which have no implementation and must be overridden in subclasses. Together, they provide a template for subclasses to fill in and act as a variant of the powerful interfaces of Java that you'll study tomorrow.
Why are there so many different levels of protection in Java? | |
Each level of protection, or visibility, provides a different view of your class to the outside world. One view is tailored for everyone, one for classes in your own package, another for your class and its subclasses only, one combining these last two and the final one for just within your class. Each is a logically well-defined and useful separation that Java supports directly in the language (as opposed to, for example, accessor methods, which are a convention you must follow). | |
Won't using accessor methods everywhere slow down my Java code? | |
Not always. As Java compilers improve and can create more optimizations, they'll be able to make them fast automatically, but if you're concerned about speed, you can always declare accessor methods to be final, and they'll be just as fast as direct instance variable accesses. | |
Are class (static) methods inherited just like instance methods? | |
No. static (class) methods are now final by default. How, then, can you ever declare a non-final class method? The answer is that you can't! Inheritance of class methods is not allowed, breaking the symmetry with instance methods. | |
Based on what I've learned, it seems like private abstract methods and final abstract methods or classes don't make sense. Are they legal? | |
Nope, they're compile-time errors, as you have guessed. To be useful, abstract methods must be overridden, and abstract classes must be subclassed, but neither of those two operations would be legal if they were also private or final. | |
What about the transient modifier? I saw that mentioned in the Java Language Specification. | |
The transient modifier is reserved by the designers of Java for use in future versions of the Java language (beyond 1.0.2 and 1.1); it will be used to create persistent object store systems (the ability to save a set of classes and objects and restore their state later on). It, like other modifiers such as byvalue, future, and generic, are not currently used but are reserved words in the language. | |
I tried creating a private variable inside a method definition. It didn't work. What did I do wrong? | |
Nothing. All the modifiers in this chapter, when you can use them with variables, only apply to class and instance variables. Local variables-those that appear inside the body of a method or loop-cannot use any of these modifiers. |