Chapter 2
IN THIS CHAPTER
Creating your own class
Looking at the pieces of a class declaration
Finding out about class fields
Constructing constructors
Adding methods to your classes
Using the this keyword
Using the new record feature in Java 14
Okay, class, it's time to learn how to create your own classes.
In this chapter, you discover the basics of creating classes in Java. All Java programs use or consist of classes, so you’ve already seen many examples of classes. You’ve seen class headers such as public class GuessingGame
and static methods such as public static void main
. Now, in this chapter, I show you how to create programs that have more than one class.
All classes must be defined by a class declaration — lines of code that provide the name for the class and the body of the class. Here’s the most basic form of a class declaration:
[public] class ClassName {class-body}
The public
keyword indicates that this class is available for use by other classes. Although it’s optional, you usually include it in your class declarations. After all, the main reason you write class declarations is so other classes can create objects from the class you’re defining. (Find out more about using the public
keyword in the section “Seeing where classes go,” later in this chapter.)
The ClassName
is a name that provides a name for your class. You can use any legal Java name you want to name a class, but the following three guidelines can simplify your life:
Ball
, RetailCustomer
, and GuessingGame
.Avoid using the name of a Java API class. No rule says that you absolutely have to, but if you create a class that has the same name as a Java API class, you have to use fully qualified names (such as java.util.Scanner
) to tell your class apart from the API class with the same name.
There are thousands of Java API classes, so avoiding them all is pretty hard. But at the least, you should avoid commonly used Java class names, as well as any API classes that your application is likely to use. Creating a class named
String
or Math
, for example, is just asking for trouble.
The class body of a class is everything that goes within the braces at the end of the class declaration. The public class ClassName
part of a class declaration takes just one line, but the body of the class declaration may take hundreds of lines (or thousands, if you get carried away).
The class body can contain the following elements:
Some programmers like to place the fields at the end of the class rather than at the beginning. Whatever brings you happiness is fine with me.
A public class must be written in a source file that has the same name as the class, with the extension java
. A public class named Greeter
, for example, must be placed in a file named Greeter.java
.
As a result, you can’t place two public classes in the same file. The following source file (named DiceGame.java
) won’t compile:
public class DiceGame
{
public static void main(String[] args)
{
Dice d = new Dice();
d.roll();
}
}
public class Dice
{
public void roll()
{
// code that rolls the dice goes here
}
}
The compiler coughs up a message indicating that Dice
is a public class and must be declared in a file named Dice.java
.
This problem has two solutions. The first is to remove the public
keyword from the Dice
class:
public class DiceGame
{
public static void main(String[] args)
{
Dice d = new Dice();
d.roll();
}
}
class Dice
{
public void roll()
{
// code that rolls the dice goes here
}
}
The compiler gladly accepts this program.
Removing the public
keyword from a class is acceptable for relatively small programs, but its limitation is that the Dice
class is available only to the classes defined within the DiceGame.java
file. If you want the Dice
class to be more widely available, opt for the second solution: Place it, with the public
keyword, in a separate file named Dice.java
.
The members of a class are the fields and methods defined in the class body. (Technically, classes and interfaces defined within a class are members too. I don’t discuss them in this chapter, though, so you can ignore them for now.)
The following sections describe the basics of working with fields and methods in your classes.
A field is a variable that’s defined in the body of a class, outside any of the class’s methods. Fields, which are also called instance variables, are available to all the methods of a class. In addition, if the field specifies the public
keyword, the field is visible outside the class. If you don’t want the field to be visible outside the class, use the private
keyword instead.
A field is defined the same as any other Java variable, but it can also have a modifier that specifies the visibility of the field — that is, whether other classes can access the fields of the class you’re defining. For now, I’ll just use two basic forms of visibility: public and private. For a more complete discussion of visibility, see the section “Understanding Visibility,” later in this chapter.
To create a public field that can be accessed by other classes, use the public
modifier:
public int trajectory = 0;
public String name;
public Player player;
To create a private field, specify private
instead of public
:
private int x_position = 0;
private int y_position = 0;
private String error-message = "";
Fields can also be declared as final
:
public final int MAX_SCORE = 1000;
The value of a final
field can't be changed after it has been initialized. Note: Spelling static final
field names with all capital letters is customary, but not required.
You define methods for a class by using the same techniques that I describe in Book 2, Chapter 7. To declare a method that’s available to users of your class, add the public
keyword to the method declaration:
public boolean isActive()
{
return this.isActive;
}
To create a private method that can be used within the class but isn’t visible outside the class, use the private
keyword:
private void calculateLunarTrajectory()
{
// code to get the calculated lunar trajectory
}
In the preceding sections, I mention that both fields and methods can use the public
or private
keyword to indicate whether the field or method can be accessed from outside the class. This is called the visibility of the field or method.
There are actually four distinct levels of visibility you can use:
private
: For fields that shouldn't be visible to any other classes — in other words, fields that are completely internal to the class.public
: For fields that should be visible to every other Java class, including classes that are outside of the current package.protected
: For fields that should be visible only to subclasses of the current class — that is, to subclasses or inner classes. (For more information, refer to Chapters 4 and 7 of this minibook.)package-private
: Use this visibility for fields that should be visible to any other class within the current package.The combination of all the members that have public
access is sometimes called the public interface of your class. These members are the only means that other objects have to communicate with objects created from your class. As a result, carefully consider which public fields and methods your class declares. (Again, I use the term interface here in a general sense, not to be confused with the specific Java feature called interface, which I cover in Chapter 5 of this minibook.)
The term expose is sometimes used to refer to the creation of public fields and methods. If a class has a public method named isActive
, for example, you could say that the class exposes the isActive
method. That simply means the method is available to other classes.
One of the basic goals of object-oriented programming is to hide the implementation details of a class inside the class while carefully controlling what aspects of the class are exposed to the outside world. This is often referred to as encapsulation. As a general rule, you hide as many of the details of your implementation from the outside world as you possibly can.
One way to do that is to avoid creating public fields. Instead, make your fields private. Then, selectively grant access to the data those fields contain by adding to the class special methods called accessors.
There are two types of accessors. A get
accessor (also called a getter) is a method that retrieves a field value, whereas a set
accessor (setter) is a method that sets a field value. These methods are usually named getFieldName
and setFieldName
, respectively. If the field is named count
, for example, the getter and setter methods are named getCount
and setCount
.
Here's a class that uses a private field named Health
to indicate the health of a player in a game program:
public class Player
{
private int health;
public int getHealth()
{
return health;
}
public void setHealth(int h)
{
health = h;
}
}
Here the health
field itself is declared as private
, so it can’t be accessed directly. Instead, it can be accessed only through the methods getHealth
and setHealth
.
Creating classes with accessors rather than simple public fields offers several benefits:
get
accessor but not a set
accessor. Then other classes can retrieve the property value — but can’t change it.get
accessor method is called. Suppose you have a class named Order
that includes fields named unitPrice
and quantityOrdered
. This class might also contain a getOrderTotal
method that looks like this:
public double getOrderTotal()
{
return unitPrice * quantityOrdered;
}
Here, instead of returning the value of a class field, the get
accessor calculates the value to be returned.
int
property named Health
whose value can range from 0
to 100
. Here’s a set accessor that prevents the Health
property from being set to an incorrect value:
public void setHealth(int h)
{
if (h < 0)
health = 0;
else if (h > 100)
health = 100;
else
health = h;
}
Here, if the setHealth
method is called with a value less than 0
, health
is set to 0
. Likewise, if the value is greater than 100
, health
is set to 100
.
For a little added insight on the use of accessors, see the nearby sidebar “The Accessor pattern.”
A Java class can contain two or more methods with the same name, provided that those methods accept different parameters. This technique, called overloading, is one of the keys to building flexibility into your classes. With overloading, you can anticipate different ways that someone might want to invoke an object’s functions and then provide overloaded methods for each alternative.
You’re already familiar with several classes that have overloaded methods, though you may not realize it. The PrintWriter
class, for example (which you access via System.out
), defines 10 versions of the println
method that allow you to print different types of data. The following lines show the method declaration for each of these overloads:
void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)
The basic rule in creating overloaded methods is that every method must have a unique signature. A method’s signature is the combination of its name and the number and types of parameters it accepts. Thus, each of the println
methods has a different signature, because although all the methods have the same name, each method accepts a different parameter type.
Two things that are not a part of a method’s signature are
double someMethodOfMine(double x, boolean y)
double someMethodOfMine(double param1, boolean param2)
A constructor is a block of code that’s called when an instance of an object is created. In many ways, a constructor is similar to a method, but a few differences exist:
new
keyword that calls the constructor. After creating the object, you can’t call the constructor again.Here’s the basic format for coding a constructor:
public ClassName (parameter-list) [throws exception…]
{
statements…
}
The public
keyword indicates that other classes can access the constructor. That’s usually what you want, although in the next chapter, you see why you might want to create a private
constructor. ClassName
must be the same as the name of the class that contains the constructor. You code the parameter list the same way that you code it for a method.
Notice also that a constructor can throw exceptions if it encounters situations that it can’t recover from. (For more information about throwing exceptions, refer to Book 2, Chapter 8.)
Probably the most common reason for coding a constructor is to provide initial values for class fields when you create the object. Suppose that you have a class named Actor
that has fields named firstName
and lastName
. You can create a constructor for the Actor
class:
public Actor(String first, String last)
{
firstName = first;
lastName = last;
}
Then you create an instance of the Actor
class by calling this constructor:
Actor a = new Actor("Arnold", "Schwarzenegger");
A new Actor
object for Arnold Schwarzenegger is created.
Like methods, constructors can be overloaded. In other words, you can provide more than one constructor for a class, provided that each constructor has a unique signature. Here’s another constructor for the Actor
class:
public Actor(String first, String last, boolean good)
{
firstName = first;
lastName = last;
goodActor = good;
}
This constructor lets you create an Actor
object with information besides the actor’s name:
Actor a = new Actor("Arnold", "Schwarzenegger", false);
I grew up watching Dragnet. I can still hear Joe Friday reading some thug his rights: “You have the right to an attorney during questioning. If you desire an attorney and cannot afford one, an attorney will be appointed to you free of charge.”
Java constructors are like that. Every class has a right to a constructor. If you don’t provide a constructor, Java appoints one for you, free of charge. This free constructor is called the default constructor. It doesn’t accept any parameters and doesn’t do anything, but it does allow your class to be instantiated.
Thus, the following two classes are identical:
public Class1
{
public Class1() { }
}
public Class1 { }
In the first example, the class explicitly declares a constructor that doesn’t accept any parameters and has no statements in its body. In the second example, Java creates a default constructor that works just like the constructor shown in the first example.
An example might clear this point up. The following code does not compile:
public class BadActorApp
{
public static void main(String[] args)
{
Actor a = new Actor(); // error: won't compile
}
}
class Actor
{
private String lastName;
private String firstName;
private boolean goodActor;
public Actor(String last, String first)
{
lastName = last;
firstName = first;
}
public Actor(String last, String first, boolean good)
{
lastName = last;
firstName = first;
goodActor = good;
}
}
This program won’t compile because it doesn’t explicitly provide a default constructor for the Actor
class; because it does provide other constructors, the default constructor isn’t generated automatically.
A constructor can call another constructor of the same class by using the special keyword this
as a method call. This technique is commonly used when you have several constructors that build on one another.
Consider this class:
public class Actor
{
private String lastName;
private String firstName;
private boolean goodActor;
public Actor(String last, String first)
{
lastName = last;
firstName = first;
}
public Actor(String last, String first, boolean good)
{
this(last, first);
goodActor = good;
}
}
Here the second constructor calls the first constructor to set the lastName
and firstName
fields. Then it sets the goodActor
field.
You have a few restrictions in using the this
keyword as a constructor call:
public Actor(String last, String first, boolean good)
{
goodActor = good;
this(last, first); // error: won't compile
}
If you try to compile a class with this constructor, you get a message saying call to this must be first statement in constructor
.
class CrazyClass
{
private String firstString;
private String secondString;
public CrazyClass(String first, String second)
{
this(first);
secondString = second;
}
public CrazyClass(String first)
{
this(first, "DEFAULT"); // error: won't compile
}
}
The first constructor starts by calling the second constructor, which calls the first constructor. The compiler complains that this error is a recursive constructor invocation
and politely refuses to compile the class.
As I describe in the preceding section, you can use the this
keyword in a constructor to call another constructor for the current class. You can also use this
in the body of a class constructor or method to refer to the current object — that is, the class instance for which the constructor or method has been called.
The this
keyword is usually used to qualify references to instance variables of the current object. For example:
public Actor(String last, String first)
{
this.lastName = last;
this.firstName = first;
}
Here this
isn’t really necessary because the compiler can tell that lastName
and firstName
refer to class variables. Suppose, however, that you use lastName
and firstName
as the parameter names for the constructor:
public Actor(String lastName, String firstName)
{
this.lastName = lastName;
this.firstName = firstName;
}
Here the this
keywords are required to distinguish among the parameters named lastName
and firstName
and the instance variables with the same names.
You can also use this
in a method body. For example:
public String getFullName()
{
return this.firstName + " " + this.lastName;
}
Because this example has no ambiguity, this
isn’t really required. Many programmers like to use this
even when it isn’t necessary, however, because it clarifies that they’re referring to an instance variable.
Sometimes you use the this
keyword all by itself to pass a reference to the current object as a method parameter. You can print the current object to the console by using the following statement:
System.out.println(this);
An initializer (sometimes called an initializer block) is a lonely block of code that’s placed outside any method, constructor, or other block of code. Initializers are executed whenever an instance of a class is created, regardless of which constructor is used to create the instance.
Initializer blocks are similar to variable initializers used to initialize variables. The difference is that with an initializer block, you can code more than one statement. Here’s a class that gets the value for a class field from the user when the class is initialized:
class PrimeClass
{
private Scanner sc = new Scanner(System.in);
public int x;
{
System.out.print(
"Enter the starting value for x: ");
x = sc.nextInt();
}
}
class PrimeClass
{
private Scanner sc = new Scanner(System.in);
public int x = getX();
private int getX()
{
System.out.print("Enter the starting value "
+ "for x: ");
return sc.nextInt();
}
}
Either way, the effect is the same.
Here are a few other tidbits of information concerning initializers:
Java 14 introduces a new type of class called a record. A record is designed to simplify the task of creating classes that consist of nothing more than a collection of data fields that — and here’s the important part — cannot be changed after the record is created. (The Java term for an object that can’t be changed after it has been created is immutable.)
You could create a class that implements this behavior using a traditional Java class as follows:
public final class Person
{
private final String firstName;
private final String lastName;
public ImmutablePersonClass(String f, String l)
{
firstName = f;
lastName = l;
}
public String firstName()
{
return firstName;
}
public String lastName()
{
return lastName;
}
}
Here, the Person
class has two private fields named firstName
and lastName
, a constructor that accepts String arguments to initialize the private fields, and getter methods that retrieve the first and last name values. When a Person
object has been created from this class, the class provides no mechanism for changing the first or last name values.
Here's a snippet of code that creates an instance of this class and then prints the full name on the console:
Person p = new Person("William", "Shakespeare");
System.out.println(p.firstName() + " " + p.lastName());
With the new record feature, you could replace the entire Person
class with the following single line of code:
public record Person(String firstName, String lastName){}
Here are the notable details you need to remember about creating records:
firstName
and lastName
.The record feature’s status as a preview feature means two things:
javac
command line to compile any programs that use the record feature and also to the java
command to run a compiled program that uses the feature.Specifically, you should use this command to compile a program that uses the record feature:
javac --enable-preview --release 14 Person.java
And to run the program, you should add the --enable-preview
switch to the java command, like this:
java --enable-preview Person.class
(Note that the --release
flag is needed on the javac
command but not on the java
command.)