Class action

Discover more about creating Java classes

In the last Java 101 column, I presented the different primitive types that Java supports. I also discussed variables, their scope and characteristics, and the various places where developers can implement them. In addition, I introduced encapsulation and the idea of using accessor methods to manipulate a class's instance variables instead of accessing them directly from outside the class.

By the end of the article, we had developed a class that looked like this:

public class AlarmClock { 
  long m_snoozeInterval = 5000; // Snooze time in millisecond 
  // Set method for m_snoozeInterval. 
  public void setSnoozeInterval(long snoozeInterval) { 
    m_snoozeInterval = snoozeInterval; 
  } 
  // Get method for m_snoozeInterval. 
  // Note that you are returning a value of type long here. 
  public long getSnoozeInterval() { 
    // Here's the line that returns the value. 
    return m_snoozeInterval; 
  } 
  public void snooze() { 
    // You can still get to m_snoozeInterval in an AlarmClock method 
    // because you are within the scope of the class. 
    System.out.println("ZZZZZ for: " + m_snoozeInterval); 
   } 
} 

Access modifiers and enforcing encapsulation

A small problem presents itself in our current AlarmClock class. Though we decided to use encapsulation and we created accessor methods for our snooze interval, we haven't yet included a way to ensure that programmers don't directly access m_snoozeInterval. Java provides an easy approach to completing this important requirement: the use of access modifiers.

You include access modifiers in the declaration of a field (a member variable or method) to control access to it. We will cover the public and private modifiers now, and other classifications in a later article.

  • The private access modifier: You can only access a private field from within the class that defines it. Only the methods of the class itself, or other parts of the class, can access the field.
  • The public access modifier: You can access a public field in the same places where you access the class that defines it. When you access the class, you can reach any public members within it.

You should not allow public access to variables, as it exposes your implementation to the world and nullifies the purpose of encapsulation.

Here's how we would restrict access to m_snoozeInterval and allow access to getSnoozeInterval():

public class AlarmClock { 
  // Restrict access to m_snoozeInterval via private
  private long m_snoozeInterval = 5000;
  // Allow access to getSnoozeInterval via public
  public long getSnoozeInterval() { 
    return m_snoozeInterval; 
  } 
  // Remainder of class as before
        
}

The Java compiler will now flag as an error any attempt to access m_snoozeInterval from code that falls outside the class. Because of the private modifier, the following code is erroneous:

public class AlarmClockTest {
  public static void main(String[] args) {
    AlarmClock ac = new AlarmClock();
        
    long snoozeInterval = ac.m_snoozeInterval;  // ERROR - Won't compile
  }
}

On the other hand, the code below is correct. We can still call getSnoozeInterval() from outside the class, which then returns the snooze interval to us.

public class AlarmClockTest {
  public static void main(String[] args) {
    AlarmClock ac = new AlarmClock();
        
    long snoozeInterval = ac.getSnoozeInterval();  // OK
  }
}

Access modifiers are a simple but powerful concept. By ensuring that other programmers follow the rules of encapsulation, it helps you write stronger code. In addition, as part of the Java security mechanisms, it aids in ensuring that code won't access sensitive parts of the Java system.

Now you understand what the public modifier before all our methods signifies. But how do we access fields that have neither a public nor a private declaration? Good question! We'll cover that in a future column when we talk more about packages.

Set the alarm clock

Our alarm clock is turning into a pretty nifty device, yet it still lacks some key functions. For example, how do we set the alarm? Let's add that functionality now.

We'll set the alarm with methods that allow us to designate the hour and minute that we want the alarm to go off. We'll assume that users will tell time with a 24-hour time system, in which time proceeds from 00:00 (0 hours and 0 minutes) to 23:59 (23 hours and 59 minutes). The hour from midnight to 1 a.m. is hour 0, and the hour from noon to 1 p.m. is hour 12, which means 23:59 is 11:59 p.m. in 12-hour clock time.

We will add methods with which you can set the hour and minute separately. For convenience, we'll also include a method to set them both at once. In addition, we will add instance variables to hold the alarm time. Our AlarmClock class will now look like this:

public class AlarmClock { 
  private long m_snoozeInterval = 5000; // Snooze time in millisecond 
  // Instance variables for alarm setting 
  private int  m_alarmHour = 0;
  private int  m_alarmMinute = 0;
  // Set method for m_snoozeInterval. 
  public void setSnoozeInterval(long snoozeInterval) { 
    m_snoozeInterval = snoozeInterval; 
  } 
  // Get method for m_snoozeInterval. 
  public long getSnoozeInterval() { 
    return m_snoozeInterval; 
  } 
  // Getter and setter methods for alarm settings
  public void setAlarmHour(int hour) {
    m_alarmHour = hour;
  }
  public int getAlarmHour() {
    return m_alarmHour;
  }
  public void setAlarmMinute(int minute) {
    m_alarmMinute = minute;
  }
  public int getAlarmMinute() {
    return m_alarmMinute;
  }
  // Set alarm hour and minute together
  // Note that we call our other setter methods to do this
  // We'll discuss why later.
  public void setAlarmTime(int hour, int minute) {
    setAlarmHour(hour);
    setAlarmMinute(minute);
  }
  public void snooze() { 
    // You can still get to m_snoozeInterval in an AlarmClock method 
    // because you are within the scope of the class. 
    System.out.println("ZZZZZ for: " + m_snoozeInterval); 
   } 
} 

Though our alarm clock features rather elementary functions, it continues to grow in complexity. Type it out yourself and give it a whirl.

public class AlarmClockTest {
  public static void main(String[] args) {
    AlarmClock ac = new AlarmClock();
    // It's an early day tomorrow - let's set the alarm.
    ac.setAlarmHour(6);
    ac.setAlarmMinute(15);
    System.out.println("Alarm setting is " + ac.getAlarmHour() + ":" +
                        ac.getAlarmMinute());
  }
}

Initialization and constructors

It is extremely important that developers learn how to initialize Java objects. You initialize objects through constructors, which are special methods called when an object is created. Every class in Java can have constructors.

Constructors are similar to regular methods, but there are two important differences between the two:

  1. Constructors have the same name as the class name.
  2. Constructors have no declared return value. This does not mean that their return value is void. It only indicates that you omit the return value part of the declaration. Consider the instance of the class itself to be the return value.

Other than these differences, constructors are similar to normal methods when written. Here's a constructor that allows us to initialize the alarm time when we create an alarm clock:

public class AlarmClock { 
  private long m_snoozeInterval = 5000; // Snooze time in millisecond 
  // Instance variables for alarm setting 
  private int  m_alarmHour = 0;
  private int  m_alarmMinute = 0;
  // This is a constructor for AlarmClock.
  // Note that its name is AlarmClock
  // Note that it has no return value declared
  // Note that it looks pretty much like a method otherwise.
  public AlarmClock(int hour, int minute) {
    setAlarmTime(hour,minute);
  }
  // Rest of class as before ...
}

How do you use constructors? I was hoping you'd ask. Let's take a look at how you create instances of AlarmClock. The following code fragment represents what we used in the past:

AlarmClock ac = new AlarmClock();
ac.setAlarmHour(6);
ac.setAlarmMinute(15);

Have you been wondering throughout the previous Java 101 columns what those parentheses signify in the code new AlarmClock()? Your answer: they pass in constructor arguments. And now you get to see how they work. To create an alarm clock now, we use the same code, but add our two new arguments that pass in the alarm time while we simultaneously create the alarm clock:

AlarmClock ac = new AlarmClock(6,15);

Note that when we create a new class object using new, it automatically calls the constructor. Our test program now looks like this:

public class AlarmClockTest {
  public static void main(String[] args) {
    AlarmClock ac = new AlarmClock(6,15);
    System.out.println("Alarm setting is " + ac.getAlarmHour() + ":" +
                        ac.getAlarmMinute());
  }
}

Here's a question for you: what happens if we try to create an alarm clock using our old code? Put this line back in your test program and see what happens.

AlarmClock ac = new AlarmClock();

You should get an error message that looks something like this:

No constructor matching AlarmClock() found in class AlarmClock

What's going on here? Read on to find out.

The no-argument or default constructor

A no-argument or default constructor is a constructor that takes no arguments. Characteristics of the no-argument constructor:

  • When you create an object without using any arguments, the object calls the no-argument constructor
  • If a class has no declared constructors, then the Java compiler implicitly and instinctively provides a no-argument constructor
  • Once you declare any constructors, then the no-argument constructor is not implicitly declared for you

In our original AlarmClock class, the Java complier invisibly added the no-argument constructor to our class. It didn't do much, but because Java depends on every class always having a constructor, it had to be present. Once we added our own constructor, the compiler did not add the no-argument constructor; the error message is the result.

What should we do if we want to create an alarm clock without setting the alarm time? We can still do that, but before I show you how, we need to take another brief detour.

Method overloading

More than one method with the same name can be visible within a class. This is called method overloading. Follow these rules when completing this approach:

  • The methods must have different parameter lists, differing either in the number or types of parameters
  • Two methods that have the same name and same parameter list result in an error
  • The methods may have different return types

Below you will find an example from one of the built-in Java classes. You may recognize the println() method, which we've been using to output to the console. This is the class that defines it:

// Much detail omitted
public class PrintStream {
// Much detail omitted ...
public void println(Object obj) { /* ... */ }
public void println(String s) { /* ... */ }
public void println(char s[]) { /* ... */ }
public void println(char c) { /* ... */ }
public void println(int i) { /* ... */ }
public void println(long l) { /* ... */ }
// etc.
}

Take a look at the PrintStream class in the Java documentation found in Resources. You'll find a println() method for each of the basic types.

When an overloaded method is called, the actual arguments to the call are used at compile time to determine which method will be invoked. Some rules exist for this also:

  • Java looks at the parameters and uses that method whose parameter list most closely matches the passed parameters.
  • Matching is quite complicated. It involves possible conversion between types. If you want all the gory details, take a look at the Java Language Specification in Resources.
  • The return type does not resolve the call.

An example of calling println() follows below. As you can see, you can invoke it with String or int. All you need to do is pass in the appropriate argument, and it calls the method.

class Example {
public static void main(String[] args)
{
// Call println(String)
System.out.println("2 + 2 =");
// Call println(int)
System.out.println(4);
}
}

Overloading constructors

Now, back to our AlarmClock class. Recall, before our short detour, that we wanted to create an alarm clock without setting the alarm time. We can complete this task quite easily. Constructors can be overloaded in the same way that regular methods can, so we'll just add in a second constructor that is a no-argument constructor.

Here's the code:

public class AlarmClock { 
  private long m_snoozeInterval = 5000; // Snooze time in millisecond 
  // Instance variables for alarm setting 
  private int  m_alarmHour = 0;
  private int  m_alarmMinute = 0;
  // Constructor for AlarmClock that takes arguments
  public AlarmClock(int hour, int minute) {
    setAlarmTime(hour,minute);
  }
  // No-argument Constructor for AlarmClock 
  public AlarmClock() {
    // Nothing for us to do here.
  }
  // Rest of class as before ...
}

Now we can create alarm clocks in two ways:

1 2 Page 1
Page 1 of 2