Java Tutorials Made Easy banner

  

A class is a construct that associates code with data. Stated differently, a class allows you to specify a type for data that cannot be simply modeled by a primitive.

 

Section 1 - The Needs for Classes

 

Suppose you need to model a person’s vital statistics. Using primitives, you would need to provide one double variable for the person’s height and another for the person’s weight:

 

    double joesHeight = 65;
    double joesWeight = 175;

You can use the following method to print a person’s vital statistics:

 

    public static void printVitalStatistics(double height, 
                                            double weight)
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }

 

What happens when we try to print Joe’s vital statistics as follows:

 

    printVitalStatistics(joesWeight, joesHeight);

 

OUTPUT:

height = 175 weight = 65

 

The primitive variables for Joe are passed in the wrong order to the printVitalStatistics method, making Joe appear much taller and thinner than usual. 

 

As shown in the previous example, using several primitives to model one entity produces code that is difficult to maintain. If we could define a construct that completely describes a person’s vital statistics, such errors would be avoided, and our code would be simplified. A class is such a construct.

 

Listing 1 - VitalStatistics.java (version 1)

 

class VitalStatistics 
{
    double height;
    double weight;
}

 

The VitalStatistics class defined in Listing 1 is a user-defined type consisting of a double primitive for height and another double primitive for weight. 

 

Section 2 - Class Definitions

 

A class definition specifies the data and code that exists inside a class. It begins with the class keyword and contains class members enclosed by a pair of curly braces.

 

Figure 1 - Class Declaration Syntax

 

Figure 1 - Class Declaration Syntax

 

Listing 1 provides the definition of the VitalStatistics class which specifies class members height and weight.

 

Section 3 - Class Members

 

The members of a class consist of fields and methods

 

Methods implement the code of a class. They are a series of statements that perform a specific task.

 

Fields contain the data of a class. They are specified using variable declarations at class scope. Any variable declaration that is not inside a method specifies a field.

 

Since classes are constructs that associate code with data, the statements in a method can operate on any field declared in the class. The version of VitalStatistics provided in Listing 2 declares fields height and weight. Class method print uses System.out.println to display the values of each field.

 

Listing 2 - VitalStatistics.java (version 2)

 

class VitalStatistics 
{
    // fields
    double height;
    double weight;

    // methods
    void print()
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }
}

Since class methods have direct access to class fields, fields do not need to be passed to methods as parameters. The following version of the print method is incorrect:

    void print(double height, double weight) // INCORRECT
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }

Section 4 - Objects

 

An object is an instance (example) of a class. Unlike primitives, which are created simply by declaring a variable of the corresponding primitive type, objects must be explicitly created by the new operator. The result of the new operator is assigned to a variable whose type is the corresponding class. The syntax is shown below:

    ClassName variable = new ClassName();

The following statement creates an object of class VitalStatistics and assigns it to variable joe:

VitalStatistics joe = new VitalStatistics();

An object’s fields and methods can be accessed using the object’s variable, followed by the dot (.) operator, followed by the field or method.

        joe.height = 65;
        joe.weight = 175;
        joe.print();

The following version of VitalStatistics class puts together everything we have learned so far. Note that since it is no longer necessary to pass height and weight as parameters to a method, it is no longer possible to reverse them.

 

Listing 3 - VitalStatistics.java (version 3)

class VitalStatistics 
{
    // fields
    double height;
    double weight;

    // methods
    void print()
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }

    public static void main(String[] args)
    {
        VitalStatistics joe = new VitalStatistics();
        joe.height = 65;
        joe.weight = 175;
        joe.print();
    }
}

OUTPUT:

height =  65 weight = 175

 

Section 5 - The Needs for Classes (revisited)

 

Perhaps you’re still not convinced that defining your own class is the right way to model a person’s vital statistics. You could try encoding this information in a string

    String joesVitalStatistics = Double.toString(65) + " " 
                               + Double.toString(175);

and then writing a method to parse the string:

public static void printVitalStatistics(String vs)
{
    double height = Double.parseDouble(
                      vs.substring(0, vs.indexOf(" ");
    double weight = Double.parseDouble(
                      vs.substring(vs.indexOf(" ") + 1;

    System.out.println(  "height = " + height 
                      + " weight = " + weight);
}

This works in most cases, but what if you format the string with height and weight in the wrong order. 

    String joesVitalStatistics = Double.toString(175) + " " 
                               + Double.toString(65);

You then have the same problem as before. As with using two primitives, this solution produces code that is difficult to maintain, and should be avoided.

 

Section 6 - Constructors

 

The program in Listing 3 created a VitalStatistics object and used assign statements to initialize its height and weight fields.

    VitalStatistics joe = new VitalStatistics();
    joe.height = 65;
    joe.weight = 175;

If the assign statements were omitted, then object joe would have the value zero for its height and weight. The VitalStatistics class would be more robust if we could force the user to provide values for height and weight. We can do so using a constructor.

A constructor is a method whose name is the same as its class. Constructors are responsible for initializing class fields.They are called by the new operator after object creation. Constructor methods have no return type. 


The following is a constructor for the VitalStatistics class. It accepts parameter values that are assigned to height and weight, respectively.

    VitalStatistics(double h, double w)
    {
        height = h;
        weight = w;
    }

The version of the VitalStatistics class in Listing 4 uses its constructor to initialize joe’s height and weight to the desired values. The constructor initializes the VitalStatistics fields correctly, so we don’t have to worry about omitting the assign statements.

 

Listing 4 - VitalStatistics.java (version 4)

 

class VitalStatistics 
{
    // fields
    double height;
    double weight;

    // methods
    VitalStatistics(double h, double w)
    {
        height = h;
        weight = w;
    }

    void print()
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }

    public static void main(String[] args)
    {
        VitalStatistics joe = new VitalStatistics(65, 175);
        joe.print();
    }
}

Section 7 - Associating Code with Data

 

Suppose we wanted to prevent a vital statistic from being negative. Since there is no way to associate this logic with the double primitive type, we need to provide if statements for all vital statistics.

    double joesHeight = -65;
    double joesWeight = -175;
    if (joesHeight  < 0)
        joesHeight  *= -1;

Note that we have forgotten to provide the if statement to make joesWeight positive. The Java compiler will not let us know the logic is missing since joesWeight is a perfectly valid double variable.

However, we can easily place this logic inside the VitalStatistics constructor,making it impossible to create a negative vital statistic.

    VitalStatistics(double h, double w)
    {
        height = h;
        weight = w;
        if (height   < 0)
            height  *= -1;
        if (weight   < 0)
            weight *= -1;
    }

This constructor will automatically convert height and weight to positive values.

        VitalStatistics joe = new VitalStatistics(-65, -175);
        joe.print();

OUTPUT:

height =  65 weight = 175

 

We have created a new type called VitalStatistics that associates the logic of ensuring positive values with the data height and weight.

 

Section 8 - Multiple Instances


Suppose our program needs to create vital statistics for Jane as well as Joe. We can create separate instances of the VitalStatistics class by calling the new operator multiple times and assigning the result to separate variables.

    VitalStatistics joe  = new VitalStatistics(65, 175);
    VitalStatistics jane = new VitalStatistics(63, 130);
    joe.print();
    jane.print();

OUTPUT:

 

height =  65 weight = 175

height =  63 weight = 130

 

The objects referenced by variables joe and jane contain separate copies of fields height and weight. When the object referenced by joe executes the print method, it operates on a different copy of the class fields than the one referenced by jane, and therefore produces different output.

 

Section 8 - Static Fields and Methods

 

Static fields are created by placing the keyword static in front of a field declaration. Unlike instance fields (fields without the keyword static) there is only one copy of the field regardless of how many class objects are created. 

 

The following declaration creates static field count and initializes it to zero.

    static int count = 0;

Static fields should not be initialized in the class constructor, since the field’s value is needed even when no objects have been created. Therefore, the field should be initialized during declaration as shown above.

 

It is often useful to count the number of objects of a class. The static count field can be used for this purpose by incrementing count in the class constructor.

    VitalStatistics(double h, double w)
    {
        height = h;
        weight = w;
        count++;
    }

Static methods operate only on static fields. The printCount method below is declared static since it simply displays static variable count.

    static void printCount()
    {
        System.out.println("count = " + count);
    }

Static fields and methods are accessed using the class name followed by the dot (.) operator followed by the static field or method. Note that an object does not need to exist to access a static field or method.

 

The version of VitalStatistics in Listing 5 combines everything we have learned so far. Figure 2 displays the objects created and their instance and static fields.

 

Listing 5 - VitalStatistics.java (version 5)

class VitalStatistics 
{
    // instance fields
    double height;
    double weight;

    // static fields 
    static int count = 0;

    // instance methods
    VitalStatistics(double h, double w)
    {
        height = h;
        weight = w;
        if (height   < 0)
            height  *= -1;
        if (weight   < 0)
            weight *= -1;
        count++;
    }

    void print()
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }

    // static methods
    static void printCount()
    {
        System.out.println("count = " + count);
    }

    public static void main(String[] args)
    {
        System.out.println("count = " + VitalStatistics.count);
        VitalStatistics joe  = new VitalStatistics(65, 175);
        VitalStatistics jane = new VitalStatistics(63, 130);
        joe.print();
        jane.print();
        VitalStatistics.printCount();
    }
}

OUTPUT:

count = 0

height =  65 weight = 175

height =  63 weight = 130

count = 2

Figure 2 - Objects share static fields but have separate copies of instance fields



Section 9 - Accessors and Mutators

 

To make a field or method accessible outside its class and package, start its declaration with the keyword public.


A method that returns the value of a field is called an accessor. Accessors have the following form:

    public fieldType getFieldname()
    {
        return field;
    }

Since an accessor’s name begins with “get”, it is commonly known as a “getter”. An accessor for the field height is as follows:

    public double getHeight()
    {
        return height;
    }

A method that modifies the value of a field is called a mutator. Mutators have the following form:

 

    public void setFieldname(fieldType value)
    {
        field = value;
    }

Since a mutator’s name begins with “set”, it is commonly known as a “setter”. A mutator for the field height is as follows:

    public void setHeight(double value)
    {
        height = value;
        if (height   < 0)
            height  *= -1;
    }

Since the VitalStatistics class constructor ensures that its fields are nonnegative, its mutators should do so as well.

 

Section 10 - Encapsulation

 

To make a field or method accessible only inside its class, start its declaration with the keyword private. 

The fields of a class should be given private access, and accessors and mutators should be provided on an as-needed basis. This is a principle of object-oriented design called encapsulation

An encapsulated class is robust since it is not possible to modify any of its instances in an unintended way. For example, in the VitalStatistics implementation of Listing 6, 

private field access and mutator design make it impossible to set the fields to negative values. 

Listing 6 provides the final version of the VitalStatistics class. All class fields have private access. Accessors are provided for all fields, but mutators are only provided for height and weight. Providing a mutator for static field count would interfere with the object counting logic implemented in the class constructor.

 

Listing 6 - VitalStatistics.java (final version)

class VitalStatistics 
{
    // instance fields

    private double height;
    private double weight;

    // static fields 

    private static int count = 0;
 

    // instance methods

    VitalStatistics(double h, double w)
    {
        height = h;
        weight = w;
        if (height   < 0)
            height  *= -1;
        if (weight   < 0)
            weight *= -1;
        count++;
    }
 
    void print()
    {
        System.out.println(  "height = " + height 
                          + " weight = " + weight);
    }
 
    public double getHeight()
    {
        return height;
    }

    public double getWeight()
    {
        return weight;
    }

    public void setHeight(double value)
    {
        height = value;
        if (height   < 0)
            height  *= -1;
    }

    public void setWeight(double value)
    {
        weight = value;
        if (weight   < 0)
            weight  *= -1;
    }

    // static methods

    public static void printCount()
    {
        System.out.println("count = " + count);
    }
 
    public static void main(String[] args)
    {
        VitalStatistics.printCount();
        VitalStatistics joe  = new VitalStatistics(65, 175);
        VitalStatistics jane = new VitalStatistics(63, 130);
        joe.print();
        jane.print();
        System.out.println("Joe’s height is " + joe.getHeight());
        jane.setWeight(-135);
        System.out.println("Jane’s weight is " + jane.getWeight());
        VitalStatistics.printCount();
    }
}

OUTPUT:

count = 0

height =  65 weight = 175

height =  63 weight = 130

Joe’s height is 65

Jane’s weight is 135

count = 2