Objects and Classes in java chapter-4
Chapter 4
Objects and Classes
Object-oriented programming (OOP) works by modeling applications on real-world objects. The benefits of OOP, as discussed in Introduction, are real, which explains why OOP is the paradigm of choice today and why OOP languages like Java are popular. This chapter introduces you to objects and classes. If you are new to OOP, you may want to read this chapter carefully. A good understanding of OOP is the key to writing quality programs.
This chapter starts by explaining what an object is and what constitutes a class. It then teaches you how to create objects in Java using the new keyword, how objects are stored in memory, how classes can be organized into packages, how to use access control to achieve encapsulation, how the Java Virtual Machine (JVM) loads and links your objects, and how Java manages unused objects. In addition, method overloading and static class members are explained.
What Is a Java Object?
When developing an application in an OOP language, you create a model that resembles a real-life situation to solve your problem. Take for example a company payroll application, which can calculate the take home pay of an employee and the amount of income tax to be paid. An application like this would have a Company object to represent the company using the application, Employee objects that represent the employees in the company, Tax objects to represent the tax details of each employee, and so on. Before you can start programming such applications, however, you need to understand what Java objects are and how to create them.
Let's begin with a look at objects in life. Objects are everywhere, living (persons, pets, etc) and otherwise (cars, houses, streets, etc); concrete (books, televisions, etc) and abstract (love, knowledge, tax rate, regulations, and so forth). Every object has two features: attributes and actions the object is able to perform. For example, the following are some of a car's attributes:
color
number of tires
plate number
number of valves
Additionally, a car can perform these actions:
run
brake
As another example, a dog has the following attributes: color, age, type, weight, etc. And it also can bark, run, urinate, sniff, etc.
A Java object also has attribute(s) and can perform action(s). In Java, attributes are called fields and actions are called methods. In other programming languages these may be known differently. For example, methods are often called functions.
Both fields and methods are optional, meaning some Java objects may not have fields but have methods and some others may have fields but not methods. Some, of course, have both attributes and methods and some have neither.
How do you create Java objects? This is the same as asking, “How do you make cars?” Cars are expensive objects that need careful design that takes into account many things, such as safety and cost-effectiveness. You need a good blueprint to make good cars. To create Java objects, you need similar blueprints: classes.
Java Classes
A class is a blueprint or a template to create objects of identical type. If you have an Employee class, you can create any number of Employee objects. To create Street objects, you need aStreet class. A class determines what kind of objects you get. For example, if you create an Employee class that has age and position fields, all Employee objects created out of thisEmployee class will have age and position fields as well. No more no less. The class determines the object.
In summary, classes are an OOP tool that enable programmers to create the abstraction of a problem. In OOP, abstraction is the act of using programming objects to represent real-world objects. As such, programming objects do not need to have the details of real-world objects. For instance, if an Employee object in a payroll application needs only be able to work and receive a salary, then the Employee class needs only two methods, work and receiveSalary. OOP abstraction ignores the fact that a real-world employee can do many other things including eat, run, kiss, and kick.
Classes are the fundamental building blocks of a Java program. All program elements in Java must reside in a class, even if you are writing a simple program that does not require Java's object-oriented features. A Java beginner needs to consider three things when writing a class:
the class name
the fields
the methods
There are other things that can be present in a class, but they will be discussed later.
A class declaration must use the keyword class followed by a class name. Also, a class has a body within braces. Here is a general syntax for a class:
class className { [class body] }
For example, Listing 4.1 shows a Java class named Employee, where the lines in bold are the class body.
Listing 4.1: The Employee class
class Employee { int age; double salary; }
Note
By convention, class names capitalize the initial of each word. For example, here are some names that follow the convention: Employee, Boss, DateUtility, PostOffice,RegularRateCalculator. This type of naming convention is known as Pascal naming convention. The other convention, the camel naming convention, capitalize the initial of each word, except the first word. Method and field names use the camel naming convention.
A class definition must be saved in a file that has the same name as the class name. The file name must also have the java extension. For instance, the Employee class in Listing 4.1 must be saved as Employee.java.
Note
In UML class diagrams, a class is represented by a rectangle that consists of three parts: the topmost part is the class name, the middle part is the list of fields, and the bottom part is the list of methods. (See Figure 4.1) The fields and methods can be hidden if showing them is not important.
Fields
Fields are variables. They can be primitives or references to objects. For example, the Employee class in Listing 4.1 has two fields, age and salary. In Chapter 2, “Language Fundamentals” you learned how to declare and initialize variables of primitive types.
However, a field can also refer to another object. For instance, an Empoyee class may have an address field of type Address, which is a class that represents a street address:
Address address;
In other words, an object can contain other objects, that is if the class of the former contains variables that reference to the latter.
Field names should follow the camel naming convention. The initial of each word in the field, except for the first word, is written with a capital letter. For example, here are some “good” field names: age, maxAge, address, validAddress, numberOfRows.
Methods
Methods define actions that a class's objects (or instances) can do. A method has a declaration part and a body. The declaration part consists of a return value, the method name, and a list of arguments. The body contains code that perform the action.
To declare a method, use the following syntax:
returnType methodName (listOfArguments)
The return type of a method can be a primitive, an object, or void. The return type void means that the method returns nothing. The declaration part of a method is also called the signature of the method.
For example, here is the getSalary method that returns a double.
double getSalary()
The getSalary method does not accept arguments.
As another example, here is a method that returns an Address object.
Address getAddress()
And, here is a method that accepts an argument:
int negate(int number)
If a method takes more than one argument, two arguments are separated by a comma. For example, the following add method takes two ints and return an int.
int add(int a, int b)
Also note that a method may have a variable number of arguments. For details, see the section, “Varargs” in Chapter 5, “Core Classes.”
The Method main
A special method called main provides the entry point to an application. An application normally has many classes and only one of the classes needs to have a main method. This method allows the class containing it to be invoked.
The signature of the main method is as follows.
public static void main(String[] args)
If you wonder why there is “public static void” before main, you will get the answer towards the end of this chapter.
In addition, you can pass arguments to main when using java to run a class. To pass arguments, type them after the class name. Two arguments are separated by a space.
java className arg1 arg2 arg3 ...
All arguments must be passed as strings. For instance, to pass two arguments, “1” and “safeMode” when running the Test class, you type this:
java Test 1 safeMode
Strings are discussed in Chapter 5, “Core Classes.”
Constructors
Every class must have at least one constructor. Otherwise, no objects could be created out of the class and the class would be useless. As such, if your class does not explicitly define a constructor, the compiler adds one for you.
A constructor is used to construct an object. A constructor looks like a method and is sometimes called a constructor method. However, unlike a method, a constructor does not have a return value, not even void. Additionally, a constructor must have the same name as the class.
The syntax for a constructor is as follows.
constructorName (listOfArguments) { [constructor body] }
A constructor may have zero argument, in which case it is called a no-argument (or no-arg, for short) constructor. Constructor arguments can be used to initialize the fields in the object.
If the Java compiler adds a no-arg constructor to a class because the class has none, the addition will be implicit, i.e. it will not be displayed in the source file. However, if there is a constructor, regardless of the number of arguments it accepts, no constructor will be added to the class by the compiler.
As an example, Listing 4.2 adds two constructors to the Employee class in Listing 4.1.
Listing 4.2: The Employee class with constructors
public class Employee { public int age; public double salary; public Employee() { } public Employee(int ageValue, double salaryValue) { age = ageValue; salary = salaryValue; } }
The second constructor is particularly useful. Without it, to assign values to age and position, you would need to write extra lines of code to initialize the fields:
employee.age = 20; employee.salary = 90000.00;
With the second constructor, you can pass the values at the same time you create an object.
new Employee(20, 90000.00);
The new keyword is new to you, but you will learn how to use it in the next section.
Class Members in UML Class Diagrams
Figure 4.1 depicts a class in a UML class diagram. The diagram provides a quick summary of all fields and methods. You could do more in UML. UML allows you to include field types and method signatures. For example, Figure 4.2 presents the Book class with five fields and one method.
Note that in a UML class diagram a field and its type is separated by a colon. A method's argument list is presented in parentheses and its return type is written after a colon.
Creating Objects
Now that you know how to write a class, it is time to learn how to create an object from a class. An object is also called an instance. The word construct is often used in lieu of create, thus constructing an Employee object. Another term commonly used is instantiate. Instantiating the Employee class is the same as creating an instance of Employee.
There are a number of ways to create an object, but the most common one is by using the new keyword. new is always followed by the constructor of the class to be instantiated. For example, to create an Employee object, you write:
new Employee();
Most of the time, you will want to assign the created object to an object variable (or a reference variable), so that you can manipulate the object later. To achieve this, you just need to declare an object reference with the same type as the object. For instance:
Employee employee = new Employee();
Here, employee is an object reference of type Employee.
Once you have an object, you can call its methods and access its fields, by using the object reference that was assigned the object. You use a period (.) to call a method or a field. For example:
objectReference.methodName objectReference.fieldName
The following code, for instance, creates an Employee object and assigns values to its age and salary fields:
Employee employee = new Employee(); employee.age = 24; employee.salary = 50000;
When an object is created, the JVM also performs initialization that assign default values to fields. This will be discussed further in the section, “Object Creation Initialization” later in this chapter.
The null Keyword
A reference variable refers to an object. There are times, however, when a reference variable does not have a value (it is not referencing an object). Such a reference variable is said to have a null value. For example, the following class level reference variable is of type Book but has not been assigned a value;
Book book; // book is null
If you declare a local reference variable within a method but do not assign an object to it, you will need to assign null to it to satisfy the compiler:
Book book = null;
Class-level reference variables will be initialized when an instance is created, therefore you do not need to assign null to them.
Trying to access the field or method of a null variable reference raises an error, such as in the following code:
Book book = null; System.out.println(book.title); // error because book is null
You can test if a reference variable is null by using the == operator. For instance.
if (book == null) { book = new Book(); } System.out.println(book.title);
Objects in Memory
When you declare a variable in your class, either in the class level or in the method level, you allocate memory space for data that will be assigned to the variable. For primitives, it is easy to calculate the amount of memory taken. For example, declaring an int costs you four bytes and declaring a long sets you back eight bytes. However, calculation for reference variables is different.
When a program runs, some memory space is allocated for data. This data space is logically divided into two, the stack and the heap. Primitives are allocated in the stack and Java objects reside in the heap.
When you declare a primitive, a few bytes are allocated in the stack. When you declare a reference variable, some bytes are also set aside in the stack, but the memory does not contain an object's data, it contains the address of the object in the heap. In other words, when you declare
Book book;
Some bytes are set aside for the reference variable book. The initial value of book is null because there is not yet object assigned to it. When you write
Book book = new Book();
you create an instance of Book, which is stored in the heap, and assign the address of the instance to the reference variable book. A Java reference variable is like a C++ pointer except that you cannot manipulate a reference variable. In Java, a reference variable is used to access the member of the object it is referring to. Therefore, if the Book class has the public reviewmethod, you can call the method by using this syntax:
book.review();
An object can be referenced by more than one reference variable. For example,
Book myBook = new Book(); Book yourBook = myBook;
The second line copies the value of myBook to yourBook. As a result, yourBook is now referencing the same Book object as myBook.
Figure 4.3 illustrates memory allocation for a Book object referenced by myBook and yourBook.
On the other hand, the following code creates two different Book objects:
Book myBook = new Book(); Book yourBook = new Book();
The memory allocation for this code is illustrated in Figure 4.4.
Now, how about an object that contains another object? For example, consider the code in Listing 4.3 that shows the Employee class that contains an Address class.
Listing 4.3: The Employee class that contains another class
package app04; public class Employee { Address address = new Address(); }
When you create an Employee object using the following code, an Address object is also created.
Employee employee = new Employee();
Figure 4.5 depicts the position of each object in the heap.
It turns out that the Address object is not really inside the Employee object. However, the address field within the Employee object has a reference to the Address object, thus allowing theEmployee object to manipulate the Address object. Because in Java there is no way of accessing an object except through a reference variable assigned the object's address, no one else can access the Address object ‘within’ the Employee object.
Java Packages
If you are developing an application that consists of different parts, you may want to organize your classes to retain maintainability. With Java, you can group related classes or classes with similar functionality in packages. For example, standard Java classes come in packages. Java core classes are in the java.lang package. All classes for performing input and output operations are members of the java.io package, and so on. If a package needs to be organized in more detail, you can create packages that share part of the name of the former. For example, the Java class library comes with the java.lang.annotation and java.lang.reflect packages. However, mind you that sharing part of the name does not make two packages related. Thejava.lang package and the java.lang.reflect package are different packages.
Package names that start with java are reserved for the core libraries. Consequently, you cannot create a package that starts with the word java. You can compile classes that belong to such a package, but you cannot run them.
In addition, packages starting with javax are meant for extension libraries that accompany the core libraries. You should not create packages that start with javax either.
In addition to class organization, packaging can avoid naming conflict. For example, an application may use the MathUtil class from company A and an identically named class from another company if both classes belong to different packages. For this purpose, by convention your package names should be based on your domain name in reverse. Therefore, Sun's package names start with com.sun. My domain name is brainysoftware.com, so it's appropriate for me to start my package name with com.brainysoftware. For example, I would place all my applets in the com.brainysoftware.applet package and my servlets in com.brainysoftware.servlet.
A package is not a physical object, and therefore you do not need to create one. To group a class in a package, use the keyword package followed by the package name. For example, the following MathUtil class is part of the com.brainysoftware.common package:
package com.brainysoftware.common; public class MathUtil { ... }
Java also introduces the term fully qualified name, which refers to a class name that carries with it its package name. The fully qualified name of a class is its package name followed by a period and the class name. Therefore, the fully qualified name of the MathUtil class that belongs to package com.sun.common is com.sun.common.MathUtil.
A class that has no package declaration is said to belong to the default package. For example, the Employee class in Listing 4.1 belongs to the default package. You should always use a package because types in the default package cannot be used by other types outside the default package (except by using a technique called reflection). It is a bad idea for a class to not have a package.
Even though a package is not a physical object, package names have a bearing on the physical location of their class source files. A package name represents a directory structure in which a period in a package name indicates a subfolder. For example, all Java source files in the com.brainysoftware.common package must reside in the common directory that is a subdirectory of the brainysoftware directory. In turn, the latter must be a subdirectory of the com directory. Figure 4.6 depicts a folder structure for thecom.brainysoftware.common.MathUtil class.
Compiling a class in a non-default package presents a challenge for beginners. To compile such a class, you need to include the package name, replacing the dot (.) with /. For example, to compile the com.brainysoftware.common.MathUtil class, change directory to the working directory (the directory which is the parent directory of com) and type
javac com/brainysoftware/common/MathUtil.java
By default, javac will place the result in the same directory structure as the source. In this case, the MathUtil.class file will be created in the com/brainysoftware/common directory.
Running a class that belongs to a package follows a similar rule: you must include the package name, replacing . with /. For example, to run the com.brainysoftware.common.MathUtilclass, type the following from your working directory.
java com/brainysoftware/common/MathUtil
The packaging of your classes also affects the visibility of your classes, as you will witness in the next section.
Note
Code samples accompanying this book are grouped into packages too. The packages are named appXX, where XX is the chapter number.
Encapsulation and Access Control
An OOP principle, encapsulation is a mechanism that protects parts of an object that need to be secure and exposes only parts that are safe to be exposed. A television is a good example of encapsulation. Inside it are thousands of electronic components that together form the parts that can receive signals and decode them into images and sound. These components are not to be exposed to users, however, so Sony and other manufacturers wrap them in a strong metallic cover that does not break easily. For a television to be easy to use, it exposes buttons that the user can touch to turn on and off the set, adjust brightness, turn up and down the volume, and so on.
Back to encapsulation in OOP, let's take as an example a class that can encode and decode messages. The class exposes two methods called encode and decode, that users of the class can access. Internally, there are dozens of variables used to store temporary values and other methods that perform supporting tasks. The author of the class hides these variables and other methods because allowing access to them may compromise the security of the encoding/decoding algorithms. Besides, exposing too many things makes the class harder to use. As you can see later, encapsulation is a powerful feature.
Java supports encapsulation through access control. Access control is governed by access control modifiers. There are four access control modifiers in Java: public, protected, private, and the default access level. Access control modifiers can be applied to classes or class members. We'll look at them in the following subsections.
Class Access Control Modifiers
In an application with many classes, a class may be instantiated and used from other classes that are members of the same package or different packages. You can control from which packages your class can be “seen” by employing an access control modifier at the beginning of the class declaration.
A class can have either the public or the default access control level. You make a class public by using the public access control modifier. A class whose declaration bears no access control modifier has default access. A public class is visible from anywhere. Listing 4.4 shows a public class named Book.
Listing 4.4: The public Book class
package app04; public class Book { String isbn; String title; int width; int height; int numberOfPages; }
The Book class is a member of the app04 package and has five fields. Since Book is public, it can be instantiated from any other classes. In fact, the majority of the classes in the Java core libraries are public classes. For example, here is the declaration of the java.io.File class:
public class File
A public class must be saved in a file that has the same name as the class, and the extension must be java. The Book class in Listing 4.4 must be saved in the Book.java file. Also, becauseBook belongs to package app04, the Book.java file must reside inside the app04 directory.
Note
A Java source file can only contain one public class. However, it can contain multiple classes that are not public.
When there is no access control modifier preceding a class declaration, the class has the default access level. For example, Listing 4.5 presents the Chapter class that has the default access level.
Listing 4.5: The Chapter class, with the default access level
package app04; class Chapter { String title; int numberOfPages; public void review() { Page page = new Page(); int sentenceCount = page.numberOfSentences; int pageNumber = page.getPageNumber(); } }
Classes with the default access level can only be used by other classes that belong to the same package. For instance, the Chapter class can be instantiated from inside the Book class because Book belongs to the same package as Chapter. However, Chapter is not visible from other packages.
For example, you can add the following getChapter method inside the Book class:
Chapter getChapter() { return new Chapter(); }
On the other hand, if you try to add the same getChapter method to a class that does not belong to the app04 package, a compile error will be raised.
Class Member Access Control Modifiers
Class members (methods, fields, constructors, etc) can have one of four access control levels: public, protected, private, and default access. The public access control modifier is used to make a class member public, the protected modifier to make a class member public, and the private modifier to make a class member private. Without an access control modifier, a class member will have the default access level.
Table 4.1 shows the visibility of each access level.
Access Level | From classes in other packages | From classes in the same package | From child classes | From the same class |
public | yes | yes | yes | yes |
protected | no | yes | yes | yes |
default | no | yes | no | yes |
private | no | no | no | yes |
Note
The default access is sometimes called package private. To avoid confusion, this book will only use the term default access.
A public class member can be accessed by any other classes that can access the class containing the class member. For example, the toString method of the java.lang.Object class is public. Here is the method signature:
public String toString()
Once you construct an Object object, you can call its toString method because toString is public.
Object obj = new Object(); obj.toString();
Recall that you access a class member by using this syntax:
referenceVariable.memberName
In the preceding code, obj is a reference variable to an instance of java.lang.Object and toString is the method defined in the java.lang.Object class.
A protected class member has a more restricted access level. It can be accessed only from
any class in the same package as the class containing the member
a child class of the class containing the member
Note
A child class is a class that extends another class. Chapter 6, “Inheritance” explains this concept.
For instance, consider the public Page class in Listing 4.6.
Listing 4.6: The Page class
package app04; public class Page { int numberOfSentences = 10; private int pageNumber = 5; protected int getPageNumber() { return pageNumber; } }
Page has two fields (numberOfSentences and pageNumber) and one method (getPageNumber). First of all, because the Page class is public, it can be instantiated from any other class. However, even if you can instantiate it, there is no guarantee you can access its members by using the referenceVariable.memberName syntax. It depends on from which class you are accessing the Page class's members.
Its getPageNumber method is protected, so it can be accessed from any classes that belong to app04, the package that houses the Page class. For example, consider the review method in the Chapter class (given in Listing 4.5).
public void review() { Page page = new Page(); int sentenceCount = page.numberOfSentences; int pageNumber = page.getPageNumber(); }
The Chapter class can access the getPageNumber method because Chapter belongs to the same package as the Page class. Therefore, Chapter can access all protected members of thePage class.
The default access allows classes in the same package access a class member. For instance, the Chapter class can access the Page class's numberOfSentences field because the Pageand Chapter classes belong to the same package. However, numberOfSentences is not accessible from a subclass of Page if the subclass belongs to a different package. This differentiates the protected and default access levels and will be explained in detail in Chapter 6, “Inheritance.”
A class's private members can only be accessed from inside the same class. For example, there is no way you can access the Page class's private pageNumber field from anywhere other than the Page class itself. However, look at the following code from the Page class definition.
private int pageNumber = 5; protected int getPageNumber() { return pageNumber; }
The pageNumber field is private, so it can be accessed from the getPageNumber method, which is defined in the same class. The return value of getPageNumber is pageNumber, which is private. Beginners are often confused by this kind of code. If pageNumber is private, why do we use it as a return value of a protected method (getPageNumber)? Note that access topageNumber is still private, so other classes cannot modify this field. However, using it as a return value of a non-private method is allowed.
How about constructors? Access levels to constructors are the same as those for fields and methods. Therefore, constructors can have public, protected, default, and private access levels. You may think that all constructors must be public because the intention of having a constructor is to make the class instantiatable. However, to your surprise, this is not so. Some constructors are made private so that their classes cannot be instantiated by using the new keyword. Private constructors are normally used in singleton classes. If you are interested in this topic, there are articles on this topic that you can find easily on the Internet.
Note
In a UML class diagram, you can include information on class member access level. Prefix a public member with +, a protected member with # and a private member with -. Members with no prefix are regarded as having the default access level. Figure 4.7 shows the Manager class with members having various access levels.
The this Keyword
You use the this keyword from any method or constructor to refer to the current object. For example, if you have a class-level field having the same name as a local variable, you can use this syntax to refer to the former:
this.field
A common use is in the constructor that accepts values used to initialize fields. Consider the Box class in Listing 4.7.
Listing 4.7: The Box class
package com.brainysoftware.jdk5.app04; public class Box { int length; int width; int height; public Box(int length, int width, int height) { this.length = length; this.width = width; this.height = height; } }
The Box class has three fields, length, width, and height. Its constructor accepts three arguments used to initialize the fields. It is very convenient to use length, width, and height as the parameter names because they reflect what they are. Inside the constructor, length refers to the length argument, not the length field. this.length refers to the class-level length field.
It is of course possible to change the argument names, such as this.
public Box (int lengthArg, int widthArg, int heightArg) { length = lengthArg; width = widthArg; height = heightArg; }
This way, the class-level fields are not shadowed by local variables and you do not need to use the this keyword to refer to the class-level fields However, using the this keyword spares you from having to think of different names for your method or constructor arguments.
Using Other Classes
It is common to use other classes from the class you are writing. Using classes in the same package as your current class is allowed by default. However, to use classes in other packages, you must first import the packages or the classes you want to use.
Java provides the keyword import to indicate that you want to use a package or a class from a package. For example, to use the java.io.File class from your code, you must have the following import statement:
package app04; import java.io.File; public class Demo { ... }
Note that import statements must come after the package statement but before the class declaration. The import keyword can appear multiple times in a class.
package app04; import java.io.File; import java.util.List; public class Demo { ... }
Sometimes you need many classes in the same package. You can import all classes in the same package by using the wild character *. For example, the following code imports all members of the java.io package.
package app04; import java.io.*; public class Demo { ... }
Now, not only can you use the java.io.File class, but you can use other members in the java.io package too. However, to make your code more readable, it is recommended that you import a package member one at a time. In other words, if you need to use both the java.io.File class and the java.io.FileReader class, it is better to have two import statements like the following than to use the * character.
import java.io.File; import java.io.FileReader;
Note
Members of the java.lang package are imported automatically. Thus, to use the java.lang.String, for example, you do not need to explicitly import the class.
The only way to use classes that belong to other packages without importing them is to use the fully qualified names of the classes in your code. For example, the following code declares the java.io.File class using its fully qualified name.
java.io.File file = new java.io.File(filename);
If you import identically-named classes from different packages, you must use the fully qualified names when declaring the classes. For example, the Java core libraries contain the classesjava.sql.Date and java.util.Date. Importing both upsets the compiler. In this case, you must write the fully qualified names of java.sql.Date and java.util.Date in your class to use them.
Note
Java classes can be deployed in a jar file. Appendix A details how to compile a class that uses other classes in a jar file. Appendix B shows how to run a Java class in a jar file. Appendix C provides instructions on the jar tool, a program that comes with the JDK to package your Java classes and related resources.
A class that uses another class is said to “depend on” the latter. A UML diagram that depicts this dependency is shown in Figure 4.8.
A dependency relationship is represented by a dashed line with an arrow. In Figure 4.8 the Book class is dependent on Chapter because the getChapter method returns a Chapterobject.
Final Variables
Java does not reserve the keyword constant to create constants. However, in Java you can prefix a variable declaration with the keyword final to make its value unchangeable. You can make both local variables and class fields final.
For example, the number of months in a year never changes, so you can write:
final int numberOfMonths = 12;
As another example, in a class that performs mathematical calculation, you can declare the variable pi whose value is equal to 22/7 (the circumference of a circle divided by its diameter, in math represented by the Greek letter π).
final float pi = (float) 22 / 7;
Once assigned a value, the value cannot change. Attempting to change it will result in a compile error.
Note that the casting (float) after 22 / 7 is needed to convert the value of division to float. Otherwise, an int will be returned and the pi variable will have a value of 3.0, instead of 3.1428.
Also note that since Java uses Unicode characters, you can simply define the variable pi as π if you don't think typing it is harder than typing pi.
final float π = (float) 22 / 7;
Note
You can also make a method final, thus prohibiting it from being overridden in a subclass. This will be discussed in Chapter 6, “Inheritance.”
Static Members
You have learned that to access a public field or method of an object, you use a period after the object reference, such as:
// Create an instance of Book Book book = new Book(); // access the review method book.review();
This implies that you must create an object first before you can access its members. However, in previous chapters, there were examples that used System.out.print to print values to the console. You may have noticed that you could call the out field without first having to construct a System object. How come you did not have to do something like this?
System ref = new System(); ref.out;
Rather, you use a period after the class name:
System.out
Java (and many OOP languages) supports the notion of static members, which are class members that can be called without first instantiating the class. The out field in java.lang.System is static, which explains why you can write System.out.
Static members are not tied to class instances. Rather, they can be called without having an instance. In fact, the method main, which acts as the entry point to a class, is static because it must be called before any object is created.
To create a static member, you use the keyword static in front of a field or method declaration. If there is an access modifier, the static keyword may come before or after the access modifier. These two are correct:
public static int a; static public int b;
However, the first form is more often used.
For example, Listing 4.8 shows the MathUtil class with a static method:
Listing 4.8: The MathUtil class
package app04; public class MathUtil { public static int add(int a, int b) { return a + b; } }
To use the add method, you can simply call it this way:
MathUtil.add(a, b)
The term instance methods/fields are used to refer to non-static methods and fields.
From inside a static method, you cannot call instance methods or instance fields because they only exist after you create an object. You can access other static methods or fields from a static method, however.
A common confusion that a beginner often encounter is when they cannot compile their class because they are calling instance members from the main method. Listing 4.9 shows such a class.
Listing 4.9: Calling non-static members from a static method
package app04; public class StaticDemo { public int b = 8; public static void main(String[] args) { System.out.println(b); } }
The line in bold causes a compile error because it attempts to access non-static field b from the main static method. There are two solutions to this.
1. Make b static
2. Create an instance of the class, then access b by using the object reference.
Which solution is appropriate depends on the situation. It often takes years of OOP experience to come up with a good decision that you're comfortable with.
Note
You can only declare a static variable in a class level. You cannot declare local static variables even if the method is static.
How about static reference variables? You can declare static reference variables. The variable will contain an address, but the object referenced is stored in the heap. For instance
static Book book = new Book();
Static reference variables provide a good way of exposing the same object that needs to be shared among other different objects.
Note
In UML class diagrams, static members are underlined. For example, Figure 4.9 shows the MathUtil class with the static method add.
Static Final Variables
In the section, “Final Variables” earlier in the chapter, you learned that you could create a final variable by using the keyword final. However, final variables at a class level or local variables will always have the same value when the program is run. If you have multiple objects of the same class with final variables, the value of the final variables in those objects will have the same values. It is more common (and also more prudent) to make a final variable static too. This way, all objects share the same value.
The naming convention for static final variables is to have them in upper case and separate two words with an underscore. For example
static final int NUMBER_OF_MONTHS = 12; static final float PI = (float) 22 / 7;
The positions of static and final are interchangeable, but it is more common to use “static final” than “final static.”
If you want to make a static final variable accessible from outside the class, you can make it public too:
public static final int NUMBER_OF_MONTHS = 12; public static final float PI = (float) 22 / 7;
To better organize your constants, sometimes you want to put all your static final variables in a class. This class most often does not have a method or other fields and is never instantiated.
For example, sometimes you want to represent a month as an int, therefore January is 1, February is 2, and so on. Then, you use the word January instead of number 1 because it's more descriptive. Listing 4.10 shows the Months class that contains the names of months and its representation.
Listing 4.10: The Months class
package app04; public class Months { public static final int JANUARY = 1; public static final int FEBRUARY = 2; public static final int MARCH = 3; public static final int APRIL = 4; public static final int MAY = 5; public static final int JUNE = 6; public static final int JULY = 7; public static final int AUGUST = 8; public static final int SEPTEMBER = 9; public static final int OCTOBER = 10; public static final int NOVEMBER = 11; public static final int DECEMBER = 12; }
In your code, you can get the representation of January by writing.
int thisMonth = Months.JANUARY;
Classes similar to Months are very common prior to Java 5. However, Java now offers the new type enum that can eliminate the need for public static final variables. enum is explain inChapter 10, “Enums.”
Static final reference variables are also possible. However, note that only the variable is final, which means once it is assigned an address to an instance, it cannot be assigned another object of the same type. The fields in the referenced object itself can be changed.
In the following line of code
public static final Book book = new Book();
book always refer to this particular instance of Book. Reassigning it to another Book object raises a compile error:
book = new Book(); // compile error
However, you can change the Book object's field value.
book.title = "No Excuses"; // assuming the title field is public
Static import
There are a number of classes in the Java core libraries that contain static final fields. One of them is the java.util.Calendar class, that has the static final fields representing days of the week (MONDAY, TUESDAY, etc). To use a static final field in the Calendar class, you must first import the Calendar class.
import java.util.Calendar;
Then, you can use it by using the notation className.staticField.
if (today == Calendar.SATURDAY)
However, you can also import static fields using the import static keywords. For example, you can do
import static java.util.Calendar.SATURDAY;
Then, to use the imported static field, you do not need the class name:
if (today == SATURDAY)
Note
The java.util.Calendar class is discussed in more detail in Chapter 5, “Core Classes.”
Variable Scope
You have seen that you can declare variables in several different places:
In a class body as class fields. Variables declared here are referred to as class-level variables.
As parameters of a method or constructor.
In a method's body or a constructor's body.
Within a statement block, such as inside a while or for block.
Now it's time to learn the scope of variables.
Variable scope refers to the accessibility of a variable. The rule is that variables defined in a block are only accessible from within the block. The scope of the variable is the block in which it is defined. For example, consider the following for statement.
for (int x = 0; x < 5; x++) { System.out.println(x); }
The variable x is declared within the for statement. As a result, x is only available from within this for block. It is not accessible or visible from anywhere else. When the JVM executes thefor statement, it creates x. When it is finished executing the for block, it destroys x. After x is destroyed, x is said to be out of scope.
Rule number 2 is a nested block can access variables declared in the outer block. Consider this code.
for (int x = 0; x < 5; x++) { for (int y = 0; y < 3; y++) { System.out.println(x); System.out.println(y); } }
The preceding code is valid because the inner for block can access x, which is declared in the outer for block.
Following the rules, variables declared as method parameters can be accessed from within the method body. Also, class-level variables are accessible from anywhere in the class.
If a method declares a local variable that has the same name as a class-level variable, the former will ‘shadow’ the latter. To access the class-level variable from inside the method body, use the this keyword.
Method Overloading
Method names are very important and should reflect what the methods do. In many circumstances, you may want to use the same name for multiple methods because they have similar functionality. For instance, the method printString may take a String argument and prints the string. However, the same class may also provide a method that prints part of a String and accepts two arguments, the String to be printed and the character position to start printing from. You want to call the latter method printString too because it does print a String, but that would be the same as the first printString method.
Thankfully, it is okay in Java to have multiple methods having the same name, as long as each method accept different sets of argument types. In other words, in our example, it is legal to have these two methods in the same class.
public String printString(String string) public String printString(String string, int offset)
This feature is called method overloading.
The return value of the method is not taken into consideration. As such, these two methods must not exist in the same class:
public int countRows(int number); public String countRows(int number);
This is because a method can be called without assigning its return value to a variable. In such situations, having the above countRows methods would confuse the compiler as it would not know which method is being called when you write
System.out.println(countRows(3));.
A trickier situation is depicted in the following methods whose signatures are very similar.
public int printNumber(int i) { return i*2; } public long printNumber(long l) { return l*3; }
It is legal to have these two methods in the same class. However, you might wonder, which method is being called if you write printNumber(3)?
The key is to recall from Chapter 2, “Language Fundamentals” that a numeric literal will be translated into an int unless it is suffixed L or l.. Therefore, printNumber(3) will invoke this method:
public int printNumber(int i)
To call the second, pass a long:
printNumber(3L);
Note
Static methods can also be overloaded.
By Value or By Reference?
You can pass primitive variables or reference variables to a method. Primitive variables are passed by value and reference variables are passed by reference. What this means is when you pass a primitive variable, the JVM will copy the value of the passed-in variable to a new local variable. If you change the value of the local variable, the change will not affect the passed in primitive variable.
If you pass a reference variable, the local variable will refer to the same object as the passed in reference variable. If you change the object referenced within your method, the change will also be reflected in the calling code. Listing 4.11 shows the ReferencePassingTest class that demonstrates this.
Listing 4.11: The ReferencePassingTest class
package app04; class Point { public int x; public int y; } public class ReferencePassingTest { public static void increment(int x) { x++; } public static void reset(Point point) { point.x = 0; point.y = 0; } public static void main(String[] args) { int a = 9; increment(a); System.out.println(a); // prints 9 Point p = new Point(); p.x = 400; p.y = 600; reset(p); System.out.println(p.x); // prints 0 } }
There are two methods in ReferencePassingTest, increment and reset. The increment method takes an int and increments it. The reset method accepts a Point object and resets its x andy fields.
Now pay attention to the main method. We passed a (whose value is 9) to the increment method. After the method invocation, we printed the value of a and you get 9, which means that the value of a did not change.
Afterwards, you create a Point object and assign the reference to p. You then initialize its fields and pass it to the reset method. The changes in the reset method affects the Point object because objects are passed by reference. As a result, when you print the value of p.x, you get 0.
Loading, Linking, and Initialization
Now that you've learned how to create classes and objects, let's take a look at what happens when the JVM executes a class.
You run a Java program by using the java tool. For example, you use the following command to run the DemoTest class.
java DemoTest
After the JVM is loaded into memory, it starts its job by invoking the DemoTest class's main method. There are three things the JVM will do next in the specified order: loading, linking, and initialization.
Loading
The JVM loads the binary representation of the Java class (in this case, the DemoTest class) to memory and may cache it in memory, just in case the class is used again in the future. If the specified class is not found, an error will be thrown and the process stops here.
Linking
There are three things that need to be done in this phase: verification, preparation, and resolution (optional). Verification means the JVM checks that the binary representation complies with the semantic requirements of the Java programming language and the JVM. If, for example, you tamper with a class file created as a result of compilation, the class file may no longer work.
Preparation prepares the specified class for execution. This involves allocating memory space for static variables and other data structured for that class.
Resolution checks if the specified class references other classes/interfaces and if the other classes/interfaces can also be found and loaded. Checks will be done recursively to the referenced classes/interfaces.
For example, if the specified class contains the following code:
MathUtil.add(4, 3)
the JVM will load, link, and initialize the MathUtil class before calling the static add method.
Or, if the following code is found in the DemoTest class:
Book book = new Book();
the JVM will load, link, and initialize the Book class before an instance of Book is created.
Note that a JVM implementation may choose to perform resolution at a later stage, i.e. when the executing code actually requires the use of the referenced class/interface.
Initialization
In this last step, the JVM initializes static variables with assigned or default values and executes static initializers (code in static blocks). Initialization occurs just before the main method is executed. However, before the specified class can be initialized, its parent class will have to be initialized. If the parent class has not been loaded and linked, the JVM will first load and link the parent class. Again, when the parent class is about to be initialized, the parent's parent will be treated the same. This process occurs recursively until the initialized class is the topmost class in the hierarchy.
For example, if a class contains the following declaration
public static int z = 5;
the variable z will be assigned the value 5. If no initialization code is found, a static variable is given a default value. Table 4.2 lists default values for Java primitives and reference variables.
Type | Default Value |
boolean | false |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
char | \u0000 |
float | 0.0f |
double | 0.0d |
object reference | null |
In addition, code in static blocks will be executed. For example, Listing 4.12 shows the StaticCodeTest class with static code that gets executed when the class is loaded. Like static members, you can only access static members from static code.
Listing 4.12: StaticCodeTest
package app04; public class StaticInitializationTest { public static int a = 5; public static int b = a * 2; static { System.out.println("static"); System.out.println(b); } public static void main(String[] args) { System.out.println("main method"); } }
If you run this class, you will see the following on your console:
static 10 main method
Object Creation Initialization
Initialization happens when a class is loaded, as described in the section “Linking, Loading, and Initialization” earlier in this chapter. However, you can also write code that performs initialization every time an instance of a class is created.
When the JVM encounters code that instantiates a class, the JVM does the following.
1. Allocates memory space for a new object, with room for the instance variables declared in the class plus room for instance variables declared in its parent classes.
2. Processes the invoked constructor. If the constructor has parameters, the JVM creates variables for these parameter and assigns them values passed to the constructor.
3. If the invoked constructor begins with a call to another constructor (using the this keyword), the JVM processes the called constructor.
4. Performs instance initialization and instance variable initialization for this class. Instance variables that are not assigned a value will be assigned default values (See Table 4.2). Instance initialization applies to code in braces:
{ // code }
5. Executes the rest of the body of the invoked constructor.
6. Returns a reference variable that refers to the new object.
Note that instance initialization is different from static initialization. The latter occurs when a class is loaded and has nothing to do with instantiation. Instance initialization, by contrast, is performed when an object is created. In addition, unlike static initializers, instance initialization may access instance variables.
For example, Listing 4.13 presents a class named InitTest1 that has the initialization section. There is also some static initialization code to give you the idea of what is being run.
Listing 4.13: The InitTest1 class
package app04; public class InitTest1 { int x = 3; int y; // instance initialization code { y = x * 2; System.out.println(y); } // static initialization code static { System.out.println("Static initialization"); } public static void main(String[] args) { InitTest1 test = new InitTest1(); InitTest1 moreTest = new InitTest1(); } }
When run, the InitTest class prints the following on the console:
Static initialization 6 6
The static initialization is performed first, before any instantiation takes place. This is where the JVM prints the “Static initialization” message. Afterwards, the InitTest1 class is instantiated twice, explaining why you see “6” twice.
The problem with having instance initialization code is this. As your class grows bigger it becomes harder to notice that there exists initialization code.
Another way to write initialization code is in the constructor. In fact, initialization code in a constructor is more noticeable and hence preferable. Listing 4.14 shows the InitTest2 class that puts initialization code in the constructor.
Listing 4.14: The InitTest2 class
package app04; public class InitTest2 { int x = 3; int y; // instance initialization code public InitTest2() { y = x * 2; System.out.println(y); } // static initialization code static { System.out.println("Static initialization"); } public static void main(String[] args) { InitTest2 test = new InitTest2(); InitTest2 moreTest = new InitTest2(); } }
The problem with this is when you have more than one constructor and each of them must call the same code. The solution is to wrap the initialization code in a method and let the constructors call them. Listing 4.15 shows this
Listing 4.15: The InitTest3 class
package app04; public class InitTest3 { int x = 3; int y; // instance initialization code public InitTest3() { init(); } public InitTest3(int x) { this.x = x; init(); } private void init() { y = x * 2; System.out.println(y); } // static initialization code static { System.out.println("Static initialization"); } public static void main(String[] args) { InitTest3 test = new InitTest3(); InitTest3 moreTest = new InitTest3(); } }
Note that the InitTest3 class is preferable because the calls to the init method from the constructors make the initialization code more obvious than if it is in an initialization block.
Comparing Objects
In real life, when I say “My car is the same as your car” I mean my car is of the same type as yours, as new as your car, has the same color, etc.
In Java, you manipulate objects by using the variables that reference them. Bear in mind that reference variables do not contain objects but rather contain addresses to the objects in the memory. Therefore, when you compare two reference variables a and b, such as in this code
if (a == b)
you are actually asking if a and b are referencing the same object, and not whether or not the objects referenced by a and b are identical.
Consider this example.
Object a = new Object(); Object b = new Object();
The type of object a references is identical to the type of object b references. However, a and b reference two different instances and a and b contains different memory addresses. Therefore, (a == b) returns false.
Comparing object references this way is hardly useful because most of the time you are more concerned with the objects, not the addresses of the objects. If what you want is compare objects, you need to look for methods specifically provided by the class to compare objects. For example, to compare two String objects, you can call its equals method. (See Chapter 5, “Core Classes”) Whether or not comparing the contents of two objects is possible depends on whether or not the corresponding class supports it. A class can support object comparison by implementing the equals and hashCode methods it inherits from java.lang.Object. (See the section “java.lang.Object” in Chapter 5)
In addition, there are utility classes you can use to compare objects. See the discussion of java.lang.Comparable and java.util.Comparator in the section “Making Your Objects Comparable and Sortable” in Chapter 11, “The Collections Framework.”
The Garbage Collector
In several examples so far, I have shown you how to create objects using the new keyword, but you have never seen code that explicitly destroys unused objects to release memory space. If you are a C++ programmer you may have wondered if I had shown flawed code, because in C++ you must destroy objects after use.
Java comes equipped with a feature called the garbage collector, which destroys unused objects and frees memory. Unused objects are defined as objects that are no longer referenced or objects whose references are already out of scope.
With this feature, Java becomes much easier than C++ because Java programmers do not need to worry about reclaiming memory space. This, however, does not entail that you may create objects as many as you want because memory is (still) limited and it takes some time for the garbage collector to start. That's right, you can still run out of memory.
Summary
OOP models applications on real-world objects. Since Java is an OOP language, objects play a central role in Java programming. Objects are created based on a template called a class. In this chapter you've learned how to write a class and class members. There are many types of class members, including three that were discussed in this chapter: fields, methods, and constructors. There are other types of Java members such as enum and inner classes, which will be covered in other chapters.
In this chapter you have also learned two powerful OOP features, abstraction and encapsulation. Abstraction in OOP is the act of using programming objects to represent real-world objects. Encapsulation is a mechanism that protects parts of an object that need to be secure and exposes only parts that are safe to be exposed. Another feature discussed in this chapter is method overloading. Method overloading allows a class to have methods with the same name as long as their signatures are sufficiently different.
Java also comes with a garbage collector that eliminates to manually destroy unused objects. Objects are garbage collected when they are out of scope or no longer referenced.
Questions
1. Name at least three element types that a class can contain.
2. What are the differences between a method and a constructor?
3. Does a class in a class diagram display its constructors?
4. What does null mean?
5. What do you use the this keyword for?
6. When you use the == operator to compare two object references, do you actually compare the contents of the objects? Why?
7. What is the scope of variables?
8. What does “out of scope” mean?
9. How does the garbage collector decide which objects to destroy?
10. What is method overloading?
Comments
Post a Comment