Classes

   

Java™ 2 Primer Plus
By Steven Haines, Steve Potts

Table of Contents
Chapter 6.  Classes


Now that you have an idea about what objects and classes are, let's formalize some terms:

  • Objects in Java are implemented as classes; they are sometimes referred to as user-defined data types or programmer defined data types

  • Object attributes are implemented as class variables or instance variables

  • Behaviors are implemented as class methods

CarObject Class

Listing 6.4 shows an object-oriented implementation of the CarObject class, and Listing 6.5 shows a new test application, CarTest3, which uses the new CarObject class.

Listing 6.4 CarObject.java
 public class CarObject {    public static final int COUPE = 1;    public static final int CONVERTIBLE = 2;    public static final int T_TOP = 3;    public static final int V4 = 1;    public static final int V6 = 2;    public static final int V8 = 3;    public static final int V10 = 4;    private int engineType;    private int bodyType;    private int topSpeed;    private int gas;    private int oil;    private boolean running;    private int currentSpeed = 0;    public CarObject() {    }    public CarObject( int engineType,                      int bodyType,                      int topSpeed ) {        this.engineType = engineType;        this.bodyType = bodyType;        this.topSpeed = topSpeed;    }    public int getEngineType() {        return this.engineType;    }    public void setEngineType( int engineType ) {        if( engineType >= V4 && engineType <= V10 ) {            this.engineType = engineType;        }    }    public int getBodyType() {        return this.bodyType;    }    public void setBodyType( int bodyType ) {        if( bodyType >= COUPE && bodyType <= T_TOP ) {            this.bodyType = bodyType;        }    }    public int getTopSpeed() {        return this.topSpeed;    }    public void setTopSpeed( int topSpeed ) {         if( topSpeed > 0 ) {            this.topSpeed = topSpeed;        }    }    public boolean isRunning() {        return this.running;    }    public int getCurrentSpeed() {        return this.currentSpeed;    }    public void turnOn() {      running = true;    }    public void turnOff() {      running = false;    }    public void accelerate() {      switch( engineType ) {      case V4:        speedUp( 2 );        break;      case V6:        speedUp( 3 );        break;      case V8:        speedUp( 4 );         break;      case V10:        speedUp( 5 );        break;      }    }    private void speedUp( int amount ) {      if( running == false ) {        // Do nothing - car is not running!        return;      }      if( ( currentSpeed + amount ) >= topSpeed ) {        currentSpeed = topSpeed;      }      else {        currentSpeed += amount;      }    }    public void decelerate() {      if( running == false ) {         // Do nothing - car is not running!         return;      }      if( ( currentSpeed - 5 ) <= 0 ) {        currentSpeed = 0;      }      else {        currentSpeed -= 5;      }    }  } 
Listing 6.5 CarTest3.java
 public class CarTest3 {      public static void main( String[] args ) {          // Define the attributes of the car          CarObject car = new CarObject( CarObject.V10,                                         CarObject.CONVERTIBLE,                                         185 );          // Do some things with the car          car.turnOn();          for( int i=0; i<10; i++ ) {              car.accelerate();              System.out.println( "Current Speed: " + car.getCurrentSpeed() );          }          for( int i=0; i<5; i++ ) {              car.decelerate();              System.out.println( "Current Speed: " + car.getCurrentSpeed() );          }          car.turnOff();      }  } 

Component Attributes

The CarObject has the component, or physical attributes defined in Table 6.4 this is a subset of the attributes defined in Table 6.1.

Table 6.4. CarObject Component Attributes

Attribute

Description

engineType

The car's engine type; one of the constants defined in the class: V4, V6, V8, V10

bodyType

The car's body type; one of the constants defined in the class: COUPE, CONVERTIBLE, T_TOP

topSpeed

The car's top speed

The component attributes are those attributes that define physical properties of an object; this could include things such as an engine object, wheels, doors, horsepower, and so on.

State Attributes

The CarObject has the state attributes defined in Table 6.5 this is a subset of the attributes defined in Table 6.2.

Table 6.5. CarObject State Attributes

Attribute

Description

gas

The amount of gas in the car's gas tank

oil

The amount of oil in the car's oil tank

currentSpeed

How fast is the car currently going

running

Is the car running

The state attributes define those attributes that change over time.

There is not a functional difference between component and state attributes, just a logical difference. It is given here as an illustration to encourage your thought process in defining your objects.

Behavior (Methods)

Table 6.6 defines the CarObject's behavioral methods.

Table 6.6. CarObject State Attributes

Method

Description

turnOn

Turns the car on

turnOff

Turns the car off

accelerate

Accelerates the car; increases the current speed

decelerate

Decelerates the car; decreases the current speed

The behavioral methods are defined to control the object and cause the object to perform some action. In this case the accelerate and decelerate methods, along with changing the current speed of the car, also perform validation checks to see if the car is running, and if accelerating or decelerating the car has an impact on the current speed.

Get/Set Methods

Why provide methods to get and set the values of attributes rather than allow them to be modified directly?

There are actually a two answers to this question:

  1. They enable you to validate the value being assigned to the attribute. In this example the engineType can have only one of four values, but because it is an int, if the user had access to the attribute directly, what is stopping him from setting it to 1000? The compiler will enforce the valid range of integers, but knows nothing about the limitation we are imposing in this class.

  2. Some values might be read only in this example the currentSpeed is read only the accelerate and decelerate methods can modify the current speed, and the user can query the car for the current speed but cannot access it directly.

Validating values being assigned to attributes is an essential function of the encapsulation object-oriented concept. Consider a Fraction class, see Listing 6.6.

Listing 6.6 Fraction.java
 public class Fraction  {      private int numerator = 1;      private int denominator = 1;      public Fraction( int numerator,                       int denominator ) {          this.numerator = numerator;          if( denominator != 0 ) {              this.denominator = denominator;          }      }      public int getNumerator() {          return this.numerator;      }      public void setNumerator( int numerator ) {          this.numerator = numerator;       }      public int getDenominator() {          return this.denominator;      }      public void setDenominator( int denominator ) {          if( denominator != 0 ) {              this.denominator = denominator;          }      }       public float getFraction() {          return ( float )numerator / ( float )denominator;      }      public static void main( String[] args ) {          Fraction f = new Fraction( 3, 4 );          System.out.println( "3/4 = " + f.getFraction() );       }  } 

Note the setDenominator method in Listing 6.6. It validates that the value set in the denominator is not zero before making the assignment. If this value was not validated, or if the denominator value was publicly accessible, then every use of the denominator would have to be checked for zero. At this point you control the valid values that the denominator can be set to, so you can program your class assuming that the value is valid, knowing that you will not allow it to ever become invalid.

The Car class controls access to all its attributes; currentSpeed and running are read only, the engineType and bodyType are restricted to a limited subset of integer values, and topSpeed is restricted to positive integer values.

Constructors

There are two suspicious looking methods in the CarObject class: CarObject() and CarObject(...). These methods have names that match the class name and have no return values.

These methods are referred to as constructors and define initialization information to use when constructing the class. Classes are constructed using the new operator that has this form:

 CarObject myCar = new CarObject();  CarObject myOtherCar = new CarObject( CarObject.V6, CarObject.COUPE, 120 ); 

Recall that primitive types are built as follows:

 int n = 5; 

This definition allocated enough memory to hold an integer and assigned the value 5 to that memory location. When classes are constructed, the following steps are performed:

  1. Allocate memory for the class

  2. Create and initialize the class variables (in this case they are all primitive types)

  3. Call the appropriate constructor for additional initialization

  4. Return a reference to the memory location allocated in step 1

Both myCar and myOtherCar are references to CarObject's in memory. myCar uses the default constructor, or the constructor that does not take any parameters, and myOtherCar uses the constructor that accepts three parameters. Constructors, just like methods, can be overloaded as many times as you want, as long as the signature is different between each constructor. After both cars are constructed, myOtherCar has a body type of CarObject.COUPE, but what about myCar?

When class variables are created, but not initialized they have the default values shown in Table 6.7.

Table 6.7. Class Variable Default Values

Data Type

Default Value

Numeric

0

boolean

false

char

0

Object

null

All numeric types (int, float, long, and so on) and char are assigned an initial value of 0. All booleans are initialized to false, and all objects (classes) are initialized to null.

So myCar has an initial body type of 0, which coincidentally is not valid!

Note

graphics/01icon18.gif

One last note about default constructors: If you do not specify any constructor, a default constructor that accepts no parameters and does not do anything, is provided for you. But if you provide one or more constructors, if you want a default constructor, you must define one yourself.


Class Scope

A class's variables and methods belong to that class's scope; they are available inside methods in that class by their name. Publicly available variables are accessible outside the class through a handle; the instance variable name followed by a dot, followed by the variable or method name:

 CarObject myCar = new CarObject();  myCar.setEngineType( CarObject.V10 ); 

Or more generically:

 objectReferenceName.objectMemberName 

Controlling Access to Members

Java enables you to control access to methods and attributes using access modifiers. A method or attribute can be defined to be public, which enables it to be accessed by external classes or be defined to be private to ensure that only methods internal to the class can access them.

The CarObject class defines all attributes to be private, and thus not accessible from outside the class. It defines a set of public methods that act as a public interface by which external classes can use the car: turnOn, accelerate, isRunning, and so on.

this Variable

All class variables are available by name in each method in the class. If a method's parameter list contains a variable with the same name as a class variable, how are the two variables differentiated? Why would you want to do this?

Each class instance has an implicit this variable that can access all class information: both class variables and class methods. Class variables and class methods can be accessed by prefacing the variable or method name with the keyword this followed by a dot. The setTopSpeed method differentiates between the parameter variable topSpeed and a class variable topSpeed in the following code segment:

 public void setTopSpeed( int topSpeed ) {      if( topSpeed > 0 ) {          this.topSpeed = topSpeed;      }  } 

Because of the this keyword, you can qualify the variable you are referencing explicitly, so you do not have to devise a different naming convention to differentiate between parameter and class variables. There are conventions that are commonly used in other programming languages, for example C++ has two: precede class variables with an underscore, _topSpeed or precede class variables with an m_, for example m_topSpeed. These conventions are perfectly acceptable, but it makes the code a little more cryptic to read. The Java programming guidelines recommend that you use the explicit this referencing for class variables when there is a naming conflict.

Constants

As previously discussed, class-level constants can be defined by using the final keyword. Typically, constants are public so that other classes can see the values. They are usually associated with a class attribute, and constants are usually static, meaning that there is only one for all instances of a class if the value cannot change, why create multiple versions of it in memory?

The CarObject class defines a set of constant values for its engine and body types:

 public static final int COUPE = 1;  public static final int CONVERTIBLE = 2;  public static final int T_TOP = 3;  public static final int V4 = 1;  public static final int V6 = 2;  public static final int V8 = 3;  public static final int V10 = 4; 

Note

graphics/01icon18.gif

Java constants have a type associated with them; in the CarObject they are all defined to be ints. This is important because the constants have the same properties in numerical computations and comparisons as their variable equivalents. Many programming languages do not enforce this constraint, and therefore constants can be misused or generate unexpected results in computations; Java protects you from this.


Composition

All the attributes of the CarObject class are primitive types, but there is nothing stopping the CarObject from having attributes that are of object types. Consider defining an Engine class and creating an instance of that Engine inside the CarObject class:

 public class Engine {    ...  }  public class Car {    private Engine myEngine;  } 

The concept of a class containing another class is composition, a class can be composed of other classes. This is powerful object-oriented programming paradigm because it enables you to create a class, such as Engine, and reuse it after you have built it once, for a CarObject for example, you never have to rebuild it, for a TruckObject for example.

Garbage Collection

What happens in Java when an object is no longer needed? In C++, when you are done using an object you must explicitly delete that object if you forget, the memory used by that object will be lost until the program is shutdown. The designers of Java saw this as a major headache and an overhead step that programmers should not be burdened with, so they devised a new programming paradigm revolving around a process known as the garbage collector. When you no longer need a variable in Java, you can assign it a null value, enable it to reference a different region in memory (either by assigning it to another object or creating a new object), or let the variable go out of scope (leave the block of code that the variable is defined in).

When there is a region in memory that is no longer accessible, it is eligible for garbage collection. Java has a process running in the background that looks for these objects, but only when the application is running low on memory. When that region of memory is orphaned, it might be reclaimed by the system immediately, or in a few minutes. Or, if the system has ample memory, it might only be reclaimed when the application shuts down. Java manages all your memory for you.

System.gc()

If you want to request garbage collection, you can make a call to the System class's gc() static method. This method call requests that garbage collection run, but it in no way forces the Java Virtual Machine to perform garbage collection.

Finalizers

If Java is managing your memory, can you do anything to cleanup your objects before they are deleted from memory? This question is derived from my C++ background, and a C++ programming practice known as smart objects. The general concept is that you create a smart object that wraps any resource you are using, for example a handle to file; you open the file in the constructor, and then close the file in the destructor (the C++ class method that is called when an object is deleted). This programming paradigm ensures that resources are never lost; a file that is opened is always closed.

Java does not have destructors, per se, but it does have a similar construct known as a finalizer. Classes can define a finalize method that can be called when an object is deleted from memory. Finalization must be explicitly enabled by calling the System class's runFinalization() method. This method call only suggests that the garbage collector call the finalize method on objects that are targeted for garbage collection before reclaiming memory.

The bottom line is that you cannot control when garbage collection is run, or if the finalizer is ever called. If you are coming from C++ and enjoyed writing smart objects, this is one limitation of the Java programming language. But do not get discouraged, I have been enveloped into the deepest regions of Java programming and this is really the only limitation that I have found that I dislike about the language. Considering that you do not have to worry about memory management in anyway, it is a good trade-off.

Before leaving garbage collection and finalizers, here is a rough outline of the lifecycle of a Java object:

  1. Memory is allocated for the object

  2. Attributes are initialized

  3. The appropriate constructor is called

  4. The object is used in your program

  5. The reference to the object is disassociated with object in memory

  6. At some point Java's garbage collection runs and sees that the memory is no longer being used

  7. The garbage collector optionally calls the object's finalize method

  8. The garbage collector frees the memory

Static Class Members

Remember that there is only one instance of static variables or methods that are defined for a class. Therefore, if you were to have 100 instances of the CarObject class, there would be only one COUPE variable in memory, shared by all the instances. Static variables are good to use for constants and those variables that are global to all instances of your class; they are sometimes used to count class instances. Personally, I reserve static variables for defining constants.

Static methods are useful because they do not require an instance of your class to be created; that is why you can call System.out.println() without having to create an instance of the System class. The out variable is static to the System class so you can access it as System.out.


       
    Top
     



    Java 2 Primer Plus
    Java 2 Primer Plus
    ISBN: 0672324156
    EAN: 2147483647
    Year: 2001
    Pages: 332

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