Final and Abstract


This section will look at two more modifiers: final and abstract. They aren't access modifiers, but this is still a good place to present them.

Final

Classes, methods, and data may be designated final. A final class may not be subclassed. A final method may not be overridden. A final variable may not be modified after it is initialized.

Final data is useful for providing constants. For example, you might have a Zebra class that provides the zebra's weight in pounds or kilograms:

class Zebra extends Mammal {   private double weightKg;   public double getWeightKg()   {     return weightKg;   }   public double getWeightLbs()   {     return weightKg * 2.2;   } }

This class uses appropriate data hiding. A zebra's weight is stored in kilos (the variable name leaves no doubt there), but users of the class never need to know that. Let's assume that eventually the class will have many methods that convert back and forth between kilos and pounds. There will be a lot of multiplying and dividing by 2.2. The standard approach to this situation is to declare a constant:

class Zebra extends Mammal {   static private final double KGS_TO_LBS = 2.2;   private double weightKg;   public double getWeightKg()   {     return weightKg;   }   public double getWeightLbs()   {     return weightKg * KGS_TO_LBS;   } }

The constant is called KGS_TO_LBS. It is static because its value is the always going to be the same for all instances of the class, so there is no benefit in giving each instance its own non-static copy. It is private because it is only for use inside the class. It is final because its value should never change under any circumstances. Constants require a little extra typing, but they are well worth the effort for three reasons:

  • They explain what they do. Someone reading the code, especially someone who doesn't recognize 2.2 as the kilogram-to-pounds conversion factor, will instantly understand the intention of a constant named KGS_TO_LBS.

  • They eliminate the need to look up or memorize conversion factors and similar values.

  • They provide protection against typos.

The third point requires an example. Suppose you aren't using constants, and it's late at night, and you're tired. Somewhere in the Zebra source code, which is now thousands of lines in length, your finger slips and you accidentally type 3.3 instead of 2.2. It could take a long time for the error to manifest itself, and when it does, you will have to soft through thousands of lines of code to find the problem.

On the other hand, suppose you are committed to using the constant. It is still late at night, and your finger slips, and you accidentally multiply by KGS_TO_LBX instead of KGS_TO_LBS. The next time you compile your code, the compiler will complain that variable KGS_TO_LBX does not exist. When you use constants, the compiler finds your typos for you.

Abstract

Classes and methods may be designated abstract; data may not.

An abstract method has no method body. All the code from the opening curly bracket through the closing curly bracket is gone, replaced with a single semicolon (;). Here is an example of an abstract method:

abstract protected double getAverage(double[] values);

The abstract keyword may be combined with the public and protected access modifiers. Here you see a method that is both abstract and protected. There is nothing unusual about the declaration part of the method. It only gets strange after the parenthesis that closes the argument list. Where you would expect to find the method body, there is only a semicolon.

When a class has an abstract method, that method's implementation will be found in the class's subclasses. In a moment you'll see an example, but first let's cover a few rules governing abstract classes and methods.

An abstract class may not be instantiated. That is, you are not allowed to call any constructor of any abstract class. Also, if a class contains any abstract methods, the class itself must be abstract. You might say that an abstract class is one that is incomplete: It lacks one or more method implementations.

Suppose you want to create several classes, all of which share some functionality and model similar real-world things. This strongly indicates that the classes should extend a common superclass, which should contain the shared functionality. Every subclass will inherit the common methods, so this is a good object-oriented design. It would not be unusual at this point to realize that there is some functionality that every subclass must have, but that every subclass should do in its own unique way.

For example, you might be writing classes to draw charts. (We won't cover graphics programming until Chapter 14, "Painting." For now, the important point is the structure, not the content, of the code.) You might decide to create a Chart superclass with subclasses, as shown in Figure 9.7.


Figure 9.7: Chart class and subclasses

The class will have an array of floats, called values, whose values are the values to be charted. The class will also need an array of colors, called colors, since the color scheme should be flexible according to the user's taste. Java actually provides you with a class called Color. Again, you won't see this class in detail until Chapter 14. For now, you only need to know that the class exists. Its package is java.awt, so the code examples that follow all import java.awt.Color.

The most important superclass method will be display(), whose argument is an array of float values to be charted. An auxiliary method will be setColorScheme(), whose argument is an array of colors. Another auxiliary method will be useColor(), which has an int argument. The argument is an index into the color scheme array. Anything subsequently drawn on the screen, until the next call to useColor(), will appear in the specified color.

So far the superclass looks like this:

package graphics; import java.awt.Color; public class Chart {   private float[]   values;  // Chart these values   private Color[]   colors;   public void setValues(float[] vals)   {     values = values;   }   public void setColorScheme(Color[] newColors)   {     colors = newColors;     display(values);   }   private void useColor(int colorIndex)   {     // Never mind how this works.     // You'll see in chapter 14.   }   public void display(float[] values)   {     for (int i=0; i<values.length; i++)     {       useColor(i);       // ??? Now what ??     }   } }

The useColor() method is private, since it is for use inside this class only. The other methods are public, since any user might want to call them. The problem is the "??? Now what ??" line in display(). It's obvious that for each value to be charted, you should set the appropriate color and then draw a region. You know how to set the color. Ignoring for the moment that you won't learn how to draw on the screen until later in the book, we have a deeper problem. A bar chart and a pie chart draw value regions in different ways. The object-oriented approach tells us that the individual subclasses should encapsulate the knowledge of how to draw appropriately.

Let's convert Chart to an abstract class:

package graphics; public abstract class Chart {   private float[]   values;  // Chart these values   private Color[]   colors;   public void setValues(float[] vals)   {     values = values;   }   public void setColorScheme(Color[] newColors)   {     colors = newColors;     display(values);   }   private void useColor(int colorIndex)   {     // Never mind how this works.     // You'll see in chapter 15.   }   public void display(float[] values)   {     for (int i=0; i<values.length; i++)     {       useColor(i);       paintRegion(i, values[i]);     }   }   protected abstract void paintRegion(int n, float value); }

You have added an abstract method: paintRegion(). Since the class now contains an abstract method, the class itself must be abstract. Any subclass that doesn't want to be abstract will have to provide an implementation of paintRegion(). Since only a non-abstract class can be constructed, the display() method in this superclass can trust that it can safely call paintRegion(). The true class of the executing object will never be Chart. It will be BarChart, or PieChart, or perhaps some other class to be written in the future. (In the last case, the new class might not be in the same package as the superclass. That's why paintRegion() is protected.)

The non-abstract subclasses won't look very interesting, because all their functionality is graphical. Graphical code won't make any sense to you for another few chapters, so here you're just going to see the skeletons of the classes. Here is PieChart:

package graphics; public class PieChart extends Chart {   protected void paint region (int n, float value)   {     // Details not shown. Paint a pie wedge.   } }

And here is BarChart:

package graphics; public class BarChart extends Chart {   protected void paint region (int n, float value)   {     // Details not shown. Paint a bar.   } }

Abstract superclasses provide an elegant structure for partitioning shared and unique functionality.




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net