User Tools

Site Tools


java:defining_classes_in_java

This is an old revision of the document!


Defining Classes in Java

We are going to write a simple class for implementing a counter similar to one of these:1)

In case you've never seen one of these bits of advanced technology before, it's a tally counter or, as I prefer to call it, a clicker counter. It has two controls: a button on top you click to advance the counter by one and another button elsewhere you press to reset the count to zero. Our goal is to build one of these in software using object-orientation.

We first have to decide what a counter thing is. One way to start building this model for a class is to start listing the public-facing behavior (or the interface) you want an object of that class to have. A pretty comprehensive list of the things you might do with a clicker-counter is:

  • click: makes the count increase by one.
  • reset: sets the count to zero.

Next we can think about what attributes we'll need to keep track of the state of a clicker-counter. In this case, it's pretty simple: all we really need is one integer to store the count value.

So, a summary of what we need so far is:

  • a click operation
  • a reset operation
  • an integer to store the count

In Java, object attributes, which together make up the state, are defined in variables called instance variables. A class definition can include as many instance variables as it needs to store the state. In our case, we are getting off easy: the clicker-counter only needs one. The operations our object will be capable of, which make up behavior, are defined using methods. We call anything belonging to a class (e.g., instance variables and methods) a member of the class.

Our first Java class

Class names in Java traditionally use CamelCase with the first letter capitalized.

Here's a Java definition for a ClickerCounter class:

ClickerCounter.java
public class ClickerCounter {
 
    int count;
 
    void click(){
        count++;
    }
 
    void reset(){
        count = 0;
    }    
}

In Java, class definitions need to go in their own files, and the files need to be called the name of the class with the .java extension.2) I have deliberately not used any comments in the definition so you can more easily see the code.

In the above, count is an attribute/instance variable that keeps track of the state of the object. the click and reset methods implement the behavior. In actuality, this is a pretty crappy class definition for reasons we'll see later. But for now it's enough to get us started

Instantiation and use

The above is just a class definition. It doesn't actually make an object we can use.

The program below shows you how to make, or instantiate a ClickerCounter and then call the object's methods to change the state of the object.

ClickerExample.java
public class ClickerExample {
 
    public static void main(String[] args) {
        var myCounter = new ClickerCounter();  // instantiate a ClickerCounter
 
        myCounter.reset();  // count is 0
        myCounter.click();  // count is 1
        System.out.println(myCounter.count);
        myCounter.click();  // count is 2
        myCounter.click();  // count is 3
        System.out.println(myCounter.count);
        myCounter.reset();  // count is 0
        System.out.println(myCounter.count);
    }    
}

Access specifiers

The ClickerCounter class definition above effectively encapsulates the state and behavior of a clicker counter. But it has issues. For example, if we change our example as follows:

ClickerExample.java
public class ClickerExample {
 
    public static void main(String[] args) {
        var myCounter = new ClickerCounter();
 
        myCounter.reset();
        myCounter.count = 492341;  // <- LOOK HERE!
        System.out.println(myCounter.count);
        myCounter.click();
        myCounter.click();
        System.out.println(myCounter.count);
    }    
}

the object will happily oblige the user's wish to set the count to some arbitrary value. That's not something you can typically do with a clicker counter. In other words, the object offers no protection of its state. This is easy enough to solve with Java's access modifiers. Let's look at a modified version of our class definition that gives us some protection using access modifiers:

ClickerCounter.java
public class ClickerCounter {
 
    private int count;
 
    public void click(){
        count++;
    }
 
    public void reset(){
        count = 0;
    }    
}

Access modifiers are placed before the start of a member. Java offers four levels of protection, and they just so happen to all start with the letter 'p':

  • package: this is what you get by default (i.e., when you don't specify anything, as in our first definition of the class). It means anything within the same package as the class definition is able to access the member.
  • public: anyone anywhere can access the member. This is a good choice for methods that are part of the class' interface.
  • private: only those of the same class can access the member.
  • protected: only those of the same _or a derived_ class can access the member. 3)

With the changes made above, if we try to run ClickerExample we will see that the code will refuse to compile. When the compiler hits the statement

myCounter.count = 492341;

it cannot access the count member of myCounter because we said it's private. Remember, we want our objects to be self-governing, and this helps make sure this is the case.

In Java programming, it's considered a best practice to make all member variables private or protected, except in some rare situations.

Methods can be at whatever access level is appropriate for their use. If it is part of the class' interface, it should be public. If it's for use only internally by the object, it will typically be private or protected.

Accessors and mutators

Accessors

If we comment out the offending line above, we will see that the program still does not compile. The reason for this is in the statement:

System.out.println(myCounter.count);

The compiler is trying to access myCounter's count member, but we said that it's private so no one would clobber it. But now we can't access the value at all!

So, once we make a member variable private, how do we get its value?? The answer is we can write a public method that simply returns the value of the member variable:

ClickerCounter.java
public class ClickerCounter {
 
    private int count;
 
    // NEW! accessor method
    public int getCount(){
        return count;
    }
 
    public void click(){
        count++;
    }
 
    public void reset(){
        count = 0;
    }    
}

This is an example of an accessor: a member method that provides read-only access to a value in your object's state. Accessor names typically consist of “get” with the name of the state value (i.e., member variable) following.

We can now change our main method as follows:

public static void main(String[] args) {
    var myCounter = new ClickerCounter();
 
    myCounter.reset();  // count is 0
    System.out.println(myCounter.getCount());
    myCounter.click();
    myCounter.click();
    System.out.println(myCounter.getCount());
}

and life if good. Accessors are sometimes called getters, but I personally try to avoid that term.

Mutators

Let's now add a feature to our counter that lets the user set the maximum count: after the counter reaches the maximum, it should reset. Just like a physical clicker counter that only counts up to, say, 10,000.

We'll need another member variable to store this upper bound, and we'll have to modify the click() method to check it.

private int maxCount; // new member variable
 
...
 
// modified click() method
public void click(){
    if (count < maxCount) {
        count++;
    } else {
        count = 0;
    }
}

Following best-practices, I made maxCount private. But now how do we set it? The answer is we write a public method that sets the value. Even better, the public method can check to make sure the value it's being set to is value. Such a method is called a mutator: an instance method that mutate the value of a state value. Mutator names typically consist of “set” followed by with the name of the state value (member variable) being modified.

// NEW! mutator for maxCount
public void setMaxCount(int newMax) {
    if (newMax > 0) {
        maxCount = newMax;
    } else {
        System.out.println("Invalid new maximum count: " + newMax);
    }
}

Mutators should checks that new values are valid. This is another example of what's needed to make an object self-governing.

Putting it all together, we now have

ClickerCounter
/**
 * Encapsulate a tally counter/clicker-counter
 */
public class ClickerCounter {
 
    // Member variables
    private int count;
    private int maxCount;
 
    // Accessors and mutators
    public int getCount() {
        return count;
    }
 
    public void setMaxCount(int newMax) {
        if (newMax > 0) {
            maxCount = newMax;
        } else {
            System.out.println("Invalid maximum count: " + newMax);
        }
    }
 
    // Interface methods
    public void click() {
        if (count < maxCount) {
            count++;
        } else {
            count = 0;
        }
    }
 
    public void reset() {
        count = 0;
    }
}

Many new programmers feel the need to write both accessors and mutators for all instance variables. Don't. To reduce the potential for introducing bugs, you should only write methods that will actually be used. If you don't need a accessor or mutator, don't write it.

Constructors

Default constructors

Instance variables have default values. For numbers, the default value is 0, for Booleans it is false, and for object references it is null. However, there will be times when you want to override these default values. For example, let's say we are counting votes for a fraudulent election and want to start our counter at 10,000 without anyone being the wiser. To do this, we will need to use a constructor: a member method that runs automatically whenever you instantiate the object.

Adding a constructor to our class definition that does the nefarious deed mentioned above looks like:

Note the syntax: A constructor does not have a return type, is declared with public access, and has the same name as the class.

So, in short, constructors are used to _initialize_ objects. They can be used for other purposes as well, but object initialization is the main reason they were invented.

Parameterized constructors

Miscellany

toString()

One thing you'll want to get into the habit of doing with your class definitions is to write a toString() method. The method should return a string representation of the object. What you want that representation is up to you. The toString() method will automatically be invoked whenever Java needs a string representation of your object. An example of one for the ClickerCounter class might look like:

public String toString() {
    return "count: " + count;
}

Now if we want to output the state of a counter using System,out.println(), we could simply write:

System.out.println(myCounter);

Writing a toString() instance method is especially important when your class is more complicated than the simple class we've been using here. To understand the magic by which this works, you need to have an understanding of inheritance in Java. But that shouldn't stop you from starting to do this now.


Copyright © 2011-2018 Mithat Konar. All rights reserved.

1)
Picture from: “Totty Clicker - Gadgets at Play.com (UK).” Play.com (UK): DVDs, Music CDs, MP3s, Video Games, Books, Electronics & Gadgets - Free Delivery. http://www.play.com/Gadgets/Gadgets/4-/11566684/Totty-Clicker/Product.html?ptsl=1&ob=Price&fb=0# (accessed January 25, 2011).
2)
There are certain kinds of classes that don't need to be in their own file, but that's something we can't discuss yet.
3)
The difference between private and protected won't be clear until you've studied class inheritance.
java/defining_classes_in_java.1599872741.txt.gz · Last modified: 2020/09/12 01:05 by mithat

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki