You can’t do anything in Java without classes, and you have already seen several classes at work. However, not all of these show off the typical features of object orientation. Take, for example, the Math class. You have seen that you can use methods of the Math class, such as Math.random, without needing to know how they are implemented—all you need to know is the name and parameters (if any). That’s the point of encapsulation, and it will certainly be true of all classes. But the Math class only encapsulates functionality; it neither needs nor hides data. Since there is no data, you do not need to worry about making objects and initializing their instance fields—there aren’t any!
In the next section, we will look at a more typical class, the Date class. You will see how to construct objects and call methods of this class.
1. Objects and Object Variables
To work with objects, you first construct them and specify their initial state. Then you apply methods to the objects.
In the Java programming language, you use constructors to construct new instances. A constructor is a special method whose purpose is to construct and initialize objects. Let us look at an example. The standard Java library contains a Date class. Its objects describe points in time, such as December 31, 1999, 23:59:59 GMT.
Constructors always have the same name as the class name. Thus, the constructor for the Date class is called Date. To construct a Date object, combine the constructor with the new operator, as follows:
This expression constructs a new object. The object is initialized to the current date and time.
If you like, you can pass the object to a method:
Alternatively, you can apply a method to the object that you just constructed. One of the methods of the Date class is the toString method. That method yields a string representation of the date. Here is how you would apply the toString method to a newly constructed Date object:
String s = new Date().toString();
In these two examples, the constructed object is used only once. Usually, you will want to hang on to the objects that you construct so that you can keep using them. Simply store the object in a variable:
Date birthday = new Date();
Figure 4.3 shows the object variable birthday that refers to the newly constructed object.
Figure 4.3 Creating a new object
There is an important difference between objects and object variables. For example, the statement
Date deadline; // deadline doesn’t refer to any object
defines an object variable, deadline, that can refer to objects of type Date. It is important to realize that the variable deadline is not an object and, in fact, does not even refer to an object yet. You cannot use any Date methods on this variable at this time. The statement
s = deadline.toString(); // not yet
would cause a compile-time error.
You must first initialize the deadline variable. You have two choices. Of course, you can initialize the variable so that it refers to a newly constructed object:
deadline = new Date();
Or you can set the variable to refer to an existing object:
deadline = birthday;
Now both variables refer to the same object (see Figure 4.4).
Figure 4.4 Object variables that refer to the same object
It is important to realize that an object variable doesn’t actually contain an object. It only refers to an object.
In Java, the value of any object variable is a reference to an object that is stored elsewhere. The return value of the new operator is also a reference. A statement such as
Date deadline = new Date();
has two parts. The expression new Date() makes an object of type Date, and its value is a reference to that newly created object. That reference is then stored in the deadline variable.
You can explicitly set an object variable to null to indicate that it currently refers to no object.
deadline = null;
if (deadline != null)
We will discuss null in more detail in Section 4.3.6, “Working with null References,” on p. 148.
2. The LocalDate Class of the Java Library
In the preceding examples, we used the Date class that is a part of the standard Java library. An instance of the Date class has a state—namely, a particular point in time.
Although you don’t need to know this when you use the Date class, the time is represented by the number of milliseconds (positive or negative) from a fixed point, the so-called epoch, which is 00:00:00 UTC, January 1, 1970. UTC is the Coordinated Universal Time, the scientific time standard which is, for practical purposes, the same as the more familiar GMT, or Greenwich Mean Time.
But as it turns out, the Date class is not very useful for manipulating the kind of calendar information that humans use for dates, such as “December 31, 1999”. This particular description of a day follows the Gregorian calendar, which is the calendar used in most countries of the world. The same point in time would be described quite differently in the Chinese or Hebrew lunar calendars, not to mention the calendar used by your customers from Mars.
The library designers decided to separate the concerns of keeping time and attaching names to points in time. Therefore, the standard Java library contains two separate classes: the Date class, which represents a point in time, and the LocatDate class, which expresses days in the familiar calendar notation. Java 8 introduced quite a few other classes for manipulating various aspects of date and time—see Chapter 6 of Volume II.
Separating time measurement from calendars is good object-oriented design. In general, it is a good idea to use different classes to express different concepts.
You do not use a constructor to construct objects of the LocatDate class. Instead, use static factory methods that call constructors on your behalf. The expression
constructs a new object that represents the date at which the object was constructed.
You can construct an object for a specific date by supplying year, month, and day:
LocatDate.of(1999, 12, 31);
Of course, you will usually want to store the constructed object in an object variable:
LocatDate newYearsEve = LocatDate.of(1999, 12, 31);
Once you have a LocatDate object, you can find out the year, month, and day with the methods getYear, getMonthVatue, and getDayOfMonth:
int year = newYearsEve.getYear(); // 1999
int month = newYearsEve.getMonthVatue(); // 12
int day = newYearsEve.getDayOfMonth(); // 31
This may seem pointless because they are the very same values that you just used to construct the object. But sometimes, you have a date that has been computed, and then you will want to invoke those methods to find out more about it. For example, the ptusDays method yields a new LocatDate that is a given number of days away from the object to which you apply it:
LocatDate aThousandDaysLater = newYearsEve.ptusDays(1000);
year = aThousandDaysLater.getYear(); // 2002
month = aThousandDaysLater.getMonthVatue(); // 09
day = aThousandDaysLater.getDayOfMonth(); // 26
The LocatDate class has encapsulated instance fields to maintain the date to which it is set. Without looking at the source code, it is impossible to know the representation that the class uses internally. But, of course, the point of encapsulation is that this doesn’t matter. What matters are the methods that a class exposes.
3. Mutator and Accessor Methods
Have another look at the ptusDays method call that you saw in the preceding section:
LocatDate aThousandDaysLater = newYearsEve.ptusDays(1000);
What happens to newYearsEve after the call? Has it been changed to be a thousand days later? As it turns out, it has not. The ptusDays method yields a new LocatDate object, which is then assigned to the aThousandDaysLater variable. The original object remains unchanged. We say that the ptusDays method does not mutate the object on which it is invoked. (This is similar to the toUpperCase method of the String class that you saw in Chapter 3. When you call toUpperCase on a string, that string stays the same, and a new string with uppercase characters is returned.)
An earlier version of the Java library had a different class for dealing with calendars, called GregorianCatendar. Here is how you add a thousand days to a date represented by that class:
GregorianCatendar someDay = new GregorianCatendar(1999, 11, 31);
//odd feature of that ctass: month numbers go from 0 to 11
Unlike the LocatDate.ptusDays method, the GregorianCatendar.add method is a mutator method. After invoking it, the state of the someDay object has changed. Here is how you can find out the new state:
year = someDay.get(Catendar.YEAR); // 2002
month = someDay.get(Catendar.MONTH) + 1; // 09
day = someDay.get(Catendar.DAY_OF_MONTH); // 26
That’s why we called the variable someDay and not newYearsEve—it no longer is new year’s eve after calling the mutator method.
In contrast, methods that only access objects without modifying them are sometimes called accessor methods. For example, LocatDate.getYear and GregorianCatendar.get are accessor methods.
We finish this section with a program that puts the LocatDate class to work. The program displays a calendar for the current month, like this:
The current day is marked with an asterisk (*). As you can see, the program needs to know how to compute the length of a month and the weekday of a given day.
Let us go through the key steps of the program. First, we construct an object that is initialized with the current date.
LocalDate date = LocatDate.now();
We capture the current month and day.
int month = date.getMonthVatue();
int today = date.getDayOfMonth();
Then we set date to the first of the month and get the weekday of that date.
date = date.minusDays(today – 1); // set to start of month
DayOfWeek weekday = date.getDayOfWeek();
int value = weekday.getValue(); // 1 = Monday, . . . , 7 = Sunday
The variable weekday is set to an object of type DayOfWeek. We call the getValue method of that object to get a numerical value for the weekday. This yields an integer that follows the international convention where the weekend comes at the end of the week, returning 1 for Monday, 2 for Tuesday, and so on. Sunday has value 7.
Note that the first line of the calendar is indented, so that the first day of the month falls on the appropriate weekday. Here is the code to print the header and the indentation for the first line:
System.out.printtn(“Mon Tue Wed Thu Fri Sat Sun”);
for (int i = 1; i < value; i++)
Now, we are ready to print the body of the calendar. We enter a loop in which date traverses the days of the month.
In each iteration, we print the date value. If date is today, the date is marked with an *. Then, we advance date to the next day. When we reach the beginning of each new week, we print a new line:
while (date.getMonthValue() == month)
if (date.getDayOfMonth() == today)
date = date.plusDays(1);
if (date.getDayOfWeek().getValue() == 1) System.out.println();
When do we stop? We don’t know whether the month has 31, 30, 29, or 28 days. Instead, we keep iterating while date is still in the current month.
Listing 4.1 shows the complete program.
As you can see, the LocalDate class makes it possible to write a calendar program that takes care of complexities such as weekdays and the varying month lengths. You don’t need to know how the LocalDate class computes months and weekdays. You just use the interface of the class—the methods such as plusDays and getDayOfWeek.
The point of this example program is to show you how you can use the interface of a class to carry out fairly sophisticated tasks without having to know the implementation details.
Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.