Object-Oriented Java - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 5. Object-Oriented Java

Programming languages have changed a great deal since the first days of application development. Back in the day, procedural languages were state-of-the-art; as a matter of fact, there are still thousands of COBOL applications in use today. As time went on, coding became more efficient, and reuse, encapsulation, abstraction, and other object-oriented characteristics became fundamental keys to application development. As languages evolved, they began to incorporate the idea of using objects within programs. The Lisp language introduced some object-oriented techniques as early as the 1970s, but true object-oriented programming did not take off in full blast until the 1990s.

Object-oriented programs consist of many different pieces of code that all work together in unison. Rather than write a program that contains a long list of statements and commands, an object-oriented philosophy is to break functionality up into separate organized objects. Programming techniques such as using methods to encapsulate functionality and reusing the functionality of another class began to catch on as people noticed that object orientation equated to productivity.

In this chapter, we touch upon some of the key object-oriented features of the Java language. From the basic recipes covering access modifiers, to the advanced recipes that deal with inner classes, this chapter contains recipes that will help you understand Java’s object-oriented methodologies.

5-1. Controlling Access to Members of a Class

Problem

You want to create members of a class that are not accessible from any other class.

Solution

Create private instance members rather than making them available to other classes (public or protected). For instance, suppose you are creating an application that will be used to manage a team of players for a sport. You create a class named Player that will be used to represent a player on the team. You do not want the fields for that class to be accessible from any other class. The following code demonstrates the declaration of some instance members, making them accessible only from within the class in which they were defined.

private String firstName = null;
private String lastName = null;
private String position = null;
private int status = -1;

How It Works

To designate a class member as private, prefix its declaration or signature using the private keyword. The private access modifier is used to hide members of a class so that outside classes cannot access them. Any members of a class that are marked as private will be available only to other members of the same class. Any outside class will not be able to access fields or methods designated as private, and an IDE that uses code completion will not be able to see them.

As mentioned in the solution to this recipe, there are three different access modifiers that can be used when declaring members of a class. Those modifiers are public, protected, and private. Members that are declared as public are available for any other class. Those that are declared as protected are available for any other class within the same package. It is best to declare public or protected only those class members that need to be directly accessed from another class. Hiding members of a class using the private access modifier helps to enforce better object orientation.

5-2. Making Private Fields Accessible To Other Classes

Problem

You would like to create private instance members so that outside classes cannot access them directly. However, you would also like to make those private members accessible via a controlled method.

Solution

Encapsulate the private fields by making getters and setters to access them. The following code demonstrates the declaration of a private field, followed by accessor (getter) and mutator (setter) methods that can be used to obtain or set the value of that field from an outside class:

private String firstName = null;

/**
* @return the firstName
*/
public String getFirstName() {
return firstName;
}

/**
* @param firstName the firstName to set
*/
public void setFirstName(String firstName) {
this.firstName = firstName;
}

The getFirstName() method can be used by an outside class to obtain the value of the firstName field. Likewise, the setFirstName(String firstName) method can be used by an outside class to set the value of the firstName field.

How It Works

Oftentimes when fields are marked as private within a class, they still need to be made accessible to outside classes for the purpose of setting or retrieving their value. Why not just work with the fields directly and make them public then? It is not good programming practice to work directly with fields of other classes because by using accessors (getters) and mutators (setters), access can be granted in a controlled fashion. By not coding directly against members of another class, you also decouple the code, which helps to ensure that if one object changes, others that depend upon it are not adversely affected. As you can see from the example in the solution to this recipe, hiding fields and working with public methods to access those fields is fairly easy. Simply create two methods; one is used to obtain the value of the private field, the “getter” or accessor method. And the other is used to set the value of the private field, the “setter” or mutator method. In the solution to this recipe, the getter is used to return the unaltered value that is contained within the private field. Similarly, the setter is used to set the value of the privatefield by accepting an argument that is of the same data type as the private field and then setting the value of the private field to the value of the argument.

The class that is using the getters or setters for access to the fields does not know any details behind the methods. Furthermore, the details of these methods can be changed without altering any code that accesses them.

Image Note Using getters and setters does not completely decouple code. In fact, many people argue that using getters and setters is not a good programming practice because of this reason. Objects that use the accessor methods still need to know the type of the instance field they are working against. That being said, getters and setters are a standard technique for providing external access to private instance fields of an object. To make the use of accessor methods in a more object-oriented manner, declare them within interfaces and code against the interface rather than the object itself. For more information regarding interfaces, refer to recipe 5-6.

5-3. Creating a Class with a Single Instance

Problem

You would like to create a class for which only one instance can exist in the entire application, so that all application users interact with the same instance of that class.

Solution 1

Create the class using the Singleton pattern. A class implementing the Singleton pattern allows for only one instance of the class and provides a single point of access to the instance. Suppose that you wanted to create a Statistics class that would be used for calculating the statistics for each team and player within an organized sport. It does not make sense to have multiple instances of this class within the application, so you want to create the Statistics class as a Singleton in order to prevent multiple instances from being generated. The following class represents the Singleton pattern:

package org.java8recipes.chapter5.recipe5_03;

import java.util.ArrayList;
import java.util.List;
import java.io.Serializable;

public class Statistics implements Serializable {

// Definition for the class instance
private static volatile Statistics instance = new Statistics();

private List teams = new ArrayList();

/**
* Constructor has been made private so that outside classes do not have
* access to instantiate more instances of Statistics.
*/
private Statistics(){
}

/**
* Accessor for the statistics class. Only allows for one instance of the
* class to be created.
* @return
*/
public static Statistics getInstance(){

return instance;
}

/**
* @return the teams
*/
public List getTeams() {
return teams;
}

/**
* @param teams the teams to set
*/
public void setTeams(List teams) {
this.teams = teams;
}
protected Object readResolve(){
return instance;
}
}

If another class attempts to create an instance of this class, it will use the getInstance() accessor method to obtain the Singleton instance. It is important to note that the solution code demonstrates eager instantiation, which means that the instance will be instantiated when the Singleton is loaded. For lazy instantiation, which will be instantiated upon the first request, you must take care to synchronize the getInstance() method to make it thread-safe. The following code demonstrates an example of lazy instantiation:

public static Statistics getInstance(){
synchronized(Statistics.class){
if (instance == null){
instance = new Statistics();
}
}
return instance;
}

Solution 2

First, create an enum and declare a single element named INSTANCE within it. Next, declare other fields within the enum that you can use to store the values that are required for use by your application. The following enum represents a Singleton that will provide the same abilities as solution 1:

import java.util.ArrayList;
import java.util.List;

public enum StatisticsSingleton {
INSTANCE;

private List teams = new ArrayList();

/**
* @return the teams
*/
public List getTeams() {
return teams;
}

/**
* @param teams the teams to set
*/
public void setTeams(List teams) {
this.teams = teams;
}
}

Image Note There is a test class within the recipe5_03 package that you can use to work with the enum Singleton solution.

How It Works

The Singleton pattern is used to create classes that cannot be instantiated by any other class. This can be useful when you only want one instance of a class to be used for the entire application. The Singleton pattern can be applied to a class by following three steps. First, make the constructor of the class private so that no outside class can instantiate it. Next, define a private static volatile field that will represent an instance of the class. The volatile keyword guarantees each thread uses the same instance. Create an instance of the class and assign it to the field. In the solution to this recipe, the class name is Statistics, and the field definition is as follows:

private static volatile Statistics instance = new Statistics();

Last, implement an accessor method called getInstance() that simply returns the instance field. The following code demonstrates such an accessor method:

public static Statistics getInstance(){
return instance;
}

To use the Singleton from another class, call the Singleton’s getInstance() method. This will return an instance of the class. The following code shows an example of another class obtaining an instance to the Statistics Singleton that was defined in solution 1 to this recipe.

Statistics statistics = Statistics.getInstance();
List teams = statistics.getTeams();

Any class that calls the getInstance() method of the class will obtain the same instance. Therefore, the fields contained within the Singleton have the same value for every call to getInstance() within the entire application.

What happens if the Singleton is serialized and then de-serialized? This situation may cause another instance of the object to be returned upon deserialization. To prevent this issue from occurring, be sure to implement the readResolve() method, as demonstrated in solution 1. This method is called when the object is deserialized, and simply returning the instance ensures that another instance is not generated.

Solution 2 demonstrates a different way to create a Singleton, which is to use a Java enum rather than a class. Using this approach can be beneficial because an enum provides serialization, prohibits multiple instantiation, and allows you to work with code more concisely. In order to implement the enum Singleton, create an enum and declare an INSTANCE element. This is a static constant that will return an instance of the enum to classes that reference it. You can then add elements to the enum that can be used by other classes within the application to store values.

As with any programming solution, there is more than one way to do things. Some believe that the standard Singleton pattern demonstrated in solution 1 is not the most desirable solution. Others do not like the enum solution for different reasons. Both of them will work, although you may find that one works better than the other in certain circumstances.

5-4. Generating Instances of a Class

Problem

In one of your applications, you would like to provide the ability to generate instances of an object on the fly. Each instance of the object should be ready to use, and the object creator should not need to know about the details of the object creation.

Solution

Make use of the factory method pattern to instantiate instances of the class while abstracting the creation process from the object creator. Creating a factory will enable new instances of a class to be returned upon invocation. The following class represents a simple factory that returns a new instance of a Player subclass each time its createPlayer(String) method is called. The subclass of Player that is returned depends upon what String value is passed to the createPlayer method.

public class PlayerFactory {

public static Player createPlayer(String playerType){
Player returnType;
switch(playerType){
case "GOALIE":
returnType = new Goalie();
break;
case "LEFT":
returnType = new LeftWing();
break;
case "RIGHT":
returnType = new RightWing();
break;
case "CENTER":
returnType = new Center();
break;
case "DEFENSE":
returnType = new Defense();
break;
default:
returnType = new AllPlayer();
}
return returnType;
}
}

If a class wants to use the factory, it simply calls the static createPlayer method, passing a string value representing a new instance of Player. The following code represents one of the Player subclasses; the others could be very similar:

public class Goalie extends Player implements PlayerType {

private int totalSaves;

public Goalie(){
this.setPosition("GOALIE");
}

/**
* @return the totalSaves
*/
public int getTotalSaves() {
return totalSaves;
}

/**
* @param totalSaves the totalSaves to set
*/
public void setTotalSaves(int totalSaves) {
this.totalSaves = totalSaves;
}
}

Each of the other Player subclasses is very similar to the Goalie class. The most important code to note is the factory method, createPlayer, which can be used to create new instances of the Player class.

Image Note To take this example one step further, you can limit the methods that can be accessed. You do this by returning objects of type PlayerType, and only declaring the accessible methods within that interface.

How It Works

Factories are used to generate objects. Usually they are used to abstract the actual creation of an object from its creators. This can come in very handy when the creator does not need to know about the actual implementation details of generating the new object. The factory pattern can also be useful when controlled access to the creation of an object is required. In order to implement a factory, create a class that contains at least one method that is used for returning a newly created object.

In the solution to this recipe, the PlayerFactory class contains a method named createPlayer(String) that returns a newly created Player object. This method doesn’t do anything special behind the scenes; it simply instantiates a new Player instance depending upon theString value that is passed to the method. Another object that has access to the PlayerFactory class can use createPlayer to return new Player objects without knowing how the object is created. While this does not hide much in the case of the createPlayer method, thePlayerFactory abstracts the details of which class is being instantiated so that the developer only has to worry about obtaining a new Player object.

The factory pattern is an effective way to control how objects are created and makes it easier to create objects of a certain type. Imagine if a constructor for an object took more than just a handful of arguments; creating new objects that require more than just a couple of arguments can become a hassle. Generating a factory to create those objects so that you do not have to hard-code all the arguments with each instantiation can make you much more productive!

5-5. Creating Reusable Objects

Problem

You would like to generate an object that could be used to represent something within your application. For instance, suppose that you are creating an application that will be used for generating statistics and league information for different sports teams.

Solution

Create a JavaBean that can be used to represent the object that you want to create. JavaBean objects provide the capability for object fields to be declared as private, and they also allow the attributes to be read and updated so that an object can be passed around and used within an application. This recipe demonstrates the creation of a JavaBean named Team. The Team object contains a few different fields that can be used to contain information:

public class Team implements TeamType {

private List<Player> players;
private String name = null;
private String city = null;

/**
* @return the players
*/
public List<Player> getPlayers() {
return players;
}

/**
* @param players the players to set
*/
public void setPlayers(List<Player> players) {
this.players = players;
}

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the city
*/
public String getCity() {
return city;
}

/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}

}

As you can see, the object in this solution contains three fields, and each of those fields is declared as private. However, each field has two accessor methods—getters and setters—that allow the fields to be indirectly accessible.

How It Works

The JavaBean is an object that is used to hold information so that it can be passed around and used within an application. One of the most important aspects of a JavaBean is that its fields are declared as private. This prohibits other classes from accessing the fields directly. Instead, each field should be encapsulated by methods defined in order to make them accessible to other classes. These methods must adhere to the following naming conventions:

· Methods used for accessing the field data should be named using a prefix of get, followed by the field name.

· Methods used for setting the field data should be named using a prefix of set, followed by the field name.

For instance, in the solution to this recipe, the Team object contains a field with the name of players. In order to access that field, a method should be declared that is named getPlayers. That method should return the data that is contained within the players field. Likewise, to populate the players field, a method should be declared that is named setPlayers. That method should accept an argument that is of the same type as the players field, and it should set the value of the players field equal to the argument. This can be seen in the following code:

public List<Player> getPlayers() {
return players;
}

void setPlayers(List<Player> players) {
this.players = players;
}

JavaBeans can be used to populate lists of data, written to a database record, or for a myriad of other functions. Using JavaBeans makes code easier to read and maintain. It also helps to increase the likelihood of future code enhancements because very little code implementation is required. Lastly, another benefit of using JavaBeans is that most major IDEs will auto-complete the encapsulation of the fields for you.

5-6. Defining an Interface for a Class

Problem

You would like to create a set of empty method bodies and constants that can be used as a common template to expose the methods and constants that a class implements.

Solution

Generate a Java interface to declare each of the constant fields and methods that a class must implement. Such an interface can then be implemented by a class, and used to represent an object type. The following code is an interface that is used to declare the methods that must be implemented by the Team object:

public interface TeamType {

void setPlayers(List<Player> players);
void setName(String name);
void setCity(String city);
String getFullName();
}

All the methods in the interface are implicitly abstract. That is, only a method signature is provided. It is also possible to include static final variable declarations in an interface.

How It Works

A Java interface is a construct that is used to define the structures, be it variables or methods that a class must implement. In most cases, interfaces do not include any method implementations; rather, they only include method signatures. Interfaces can include variables that are implicitlystatic and final.

Image Note As of Java SE 8, it is possible for interfaces to contain method implementations. Such methods are known as default methods. See Recipe 5-7 for more details.

In the solution to this recipe, the interface does not include any constant variable declarations. However, it includes four method signatures. All the method signatures have no access modifier specified because all declarations within an interface are implicitly public. Interfaces are used to expose a set of functionality; therefore, all methods exposed within an interface must be implicitly public. Any class that implements an interface must provide the implementation for any method signatures declared in the interface, with the exception of default methods and abstract classes (see Recipes 5-7 and 5-13 for more details), in which case an interface may leave the implementation for one of its subclasses.

While the Java language does not allow multiple inheritance, a Java class can implement multiple interfaces, allowing for a controlled form of multiple inheritance. Abstract classes can also implement interfaces. The following code demonstrates a class implementing an interface: theTeam object declaration implements the TeamType interface.

public class Team implements TeamType {

private List<Player> players;
private String name;
private String city;

/**
* @return the players
*/
public List<Player> getPlayers() {
return players;
}

/**
* @param players the players to set
*/
public void setPlayers(List<Player> players) {
this.players = players;
}

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the city
*/
public String getCity() {
return city;
}

/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}

public String getFullName() {
return this.name + " - " + this.city;
}

}

Interfaces can be used to declare a type for an object. Any object that is declared to have an interface type must adhere to all the implementations declared in the interface, unless a default implementation exists. For instance, the following variable declaration defines an object that contains all the properties that are declared within the TeamType interface:

TeamType team;

Interfaces can also implement other interfaces (thus the same type of theory that is provided by multiple inheritance). However, because no method implementation is present in an interface, it is much safer to implement multiple interfaces in a Java class than it is to extend multiple classes in C++.

Interfaces are some of the single most important constructs of the Java language. They provide the interfaces between the user and the class implementations. Although it is possible to create entire applications without using interfaces, they help to promote object orientation and hide method implementations from other classes.

5-7. Modifying Interfaces without Breaking Existing Code

Problem

You’ve got a utility class that implements an interface, and many different classes within the utility library implement that interface. Suppose that you want to add a new method to the utility class and make it available for use for other classes via its interface. However, if you change the interface, it will break existing classes that already implement that interface.

Solution

Add the new method, along with its implementation, to the utility class interface as a default method. By doing so, each class that implements the interface will automatically gain use of the new method, and will not be forced to implement it since a default implementation exists. The following class interface contains a default method, which can be used by any class that implements the interface.

public interface TeamType {

List<Player> getPlayers();

void setPlayers(List<Player> players);

void setName(String name);

void setCity(String city);

String getFullName();

default void listPlayers() {
getPlayers().stream().forEach((player) -> {
System.out.println(player.getFirstName() + " " + player.getLastName());
});
}

}

The interface TeamType contains a default method named listPlayers(). This method does not need to be implemented by any classes that implement TeamType since there is a default implementation contained within the interface.

How It Works

In previous releases of Java, interfaces could only contain method signatures and constant variables. It was not possible to define a method implementation within an interface. This works well in most cases, as interfaces are a construct that is meant to enforce type safety and abstract implementation details. However, in some circumstances, it is beneficial to allow interfaces to contain a default method implementation. For instance, if there are many classes that implement an existing interface, then lots of code can be broken if that interface were to be changed. This would create a situation where backward compatibility would not be possible. In such a case, it would make sense to place a default method implementation into an interface, rather than forcing all classes to implement a new method that is placed within the interface. This is the reason why default methods became a necessity, and were included in the Java 8 release.

To create a default method (a.k.a. “defender method”) within an interface, use the keyword default within the method signature, and include a method implementation. An interface can contain zero or more default methods. In the solution to this recipe, the listPlayers() method is a default method within the TeamType interface, and any class implementing TeamType will automatically inherit the default implementation. Theoretically, any classes that implement TeamType would be completely unaffected by the addition of the listPlayers() default method. This enables one to alter an interface without breaking backward compatibility, which can be of great value.

5-8. Constructing Instances of the Same Class with Different Values

Problem

Your application requires the ability to construct instances of the same object, but each object instance needs to contain different values, thereby creating different types of the same object.

Solution

Make use of the builder pattern in order to build different types of the same object using a step-by-step procedure. For instance, suppose that you are interested in creating the different teams for a sports league. Each of the teams must contain the same attributes, but the values for those attributes vary by team. So you create many objects of the same type, but each of the objects is unique. The following code demonstrates the builder pattern, which can be used to create the required teams.

First, you need to define a set of attributes that each team needs to contain. To do this, a Java interface should be created, containing the different attributes that need to be applied to each team object. The following is an example of such an interface:

public interface TeamType {

public void setPlayers(List<Player> players);
public void setName(String name);
public void setCity(String city);
public String getFullName();

}

Next, define a class to represent a team. This class needs to implement the TeamType interface that was just created so that it will adhere to the format that is required to build a team:

public class Team implements TeamType {

private List<Player> players;
private String name = null;
private String city = null;
private int wins = 0;
private int losses = 0;
private int ties = 0;

/**
* @return the players
*/
public List<Player> getPlayers() {
return players;
}

/**
* @param players the players to set
*/
public void setPlayers(List<Player> players) {
this.players = players;
}

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the city
*/
public String getCity() {
return city;
}

/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}

public String getFullName(){
return this.name + " – " + this.city;
}

}

Now that the Team class has been defined, a builder needs to be created. The purpose of the builder object is to allow for a step-by-step creation of a team object. To abstract the details of building an object, a builder class interface should be created. The interface should define any of the methods that would be used to build the object as well as a method that will return a fully built object. In this case, the interface will define each of the methods needed to build a new Team object, and then the builder implementation will implement this interface.

public interface TeamBuilder {
public void buildPlayerList();
public void buildNewTeam(String teamName);
public void designateTeamCity(String city);
public Team getTeam();

}

The following code demonstrates a builder class implementation. Although the following code would not create a custom player list, it contains all the features required to implement the builder pattern. The details of creating a more customized player list can be worked out later, probably by allowing the user to create players via a keyboard entry. Furthermore, the TeamBuilder interface could be used to implement teams for different sports. The following class is named HockeyTeamBuilder, but a similar class implementing TeamBuilder could be namedFootballTeamBuilder, and so forth.

public class HockeyTeamBuilder implements TeamBuilder {

private Team team;

public TeamBuilder(){
this.team = new Team();
}

@Override
public void buildPlayerList() {
List players = new ArrayList();
for(int x = 0; x <= 10; x++){
players.add(PlayerFactory.getPlayer());
}
team.setPlayers(players);
}

@Override
public void buildNewTeam() {
team.setName("The Java 8 Team");
}

@Override
public void designateTeamCity(){
team.setCity("Somewhere in the world");
}

public Team getTeam(){
return this.team;
}

}

Last, use the builder by calling upon the methods defined in its interface to create teams. The following code demonstrates how this builder could be used to create one team. You can use the Roster class within the sources for this recipe to test this code:

public Team createTeam(String teamName, String city){
TeamBuilder builder = new HockeyTeamBuilder();
builder.buildNewTeam(teamName);
builder.designateTeamCity(city);
builder.buildPlayerList();
return builder.getTeam();
}

Although this demonstration of the builder pattern is relatively short, it demonstrates how to hide implementation details of an object, thereby making objects easier to build. You do not need to know what the methods within the builder actually do; you only need to call upon them.

How It Works

The builder pattern provides a way to generate new instances of an object in a procedural fashion. It abstracts away the details of object creation, so the creator does not need to do any specific work in order to generate new instances. By breaking the work down into a series of steps, the builder pattern allows objects to implement its builder methods in different ways. Because the object creator only has access to the builder methods, it makes creation of different object types much easier.

There are a few classes and interfaces that are necessary for using the builder pattern. First, you need to define a class and its different attributes. As the solution to this recipe demonstrates, the class may follow the JavaBean pattern (see Recipe 5-5 for more details). By creating a JavaBean, you will be able to populate the object by using its setters and getters. Next, you should create an interface that can be used for accessing the setters of the object that you created. Each of the setter methods should be defined in the interface, and then the object itself should implement that interface. As seen in the solution, the Team object contains the following setters, and each of them is defined in the TeamType interface:

public void setPlayers(List<Player> players);
public void setName(String name);
public void setCity(String city);

In real life, a team will probably contain more attributes. For instance, you’d probably want to set up a mascot and a home stadium name and address. The code in this example can be thought of as abbreviated because it demonstrates the creation of a generic “team object” rather than show you all the code for creating a team that is true to life. Because the Team class implements these setters that are defined within the TeamType interface, the interface methods can be called upon to interact with the actual methods of the Team class.

After the object and its interface have been coded, the actual builder needs to be created. The builder consists of an interface and its implementation class. To start, you must define the methods that you want to have other classes call upon when building your object. For instance, in the solution to this recipe, the methods buildNewTeam(), designateTeamCity(), and buildPlayerList() are defined within the builder interface named TeamBuilder. When a class wants to build one of these objects later, it will only need to call upon these defined methods in order to do it. Next, define a builder class implementation. The implementation class will implement the methods defined within the builder interface, hiding all the details of those implementations from the object creator. In the solution to this recipe, the builder class,HockeyTeamBuilder, implements the TeamBuilder interface. When a class wants to create a new Team object then it simply instantiates a new builder class.

TeamBuilder builder = new HockeyTeamBuilder();

To populate the newly created class object, the builder methods are called upon it.

builder.buildNewTeam(teamName);
builder.designateTeamCity(city);
builder.buildPlayerList();

Using this technique provides a step-by-step creation for an object. The implementation details for building that object are hidden from the object creator. It would be easy enough for a different builder implementation to use the same TeamBuilder interface for building team objects for different types. For instance, a builder implementation could be written for generating team objects for soccer, and another one could be defined for generating team objects for baseball. Each of the team object implementations would be different. However, both of them could implement the same interface—TeamBuilder—and the creator could simply call on the builder methods without caring about the details.

5-9. Interacting with a Class via Interfaces

Problem

You have created a class that implements an interface or class type. You would like to interact with the methods of that class by working with the interface rather than working directly with the class.

Solution

Declare a variable of the same type as an interface. You can then assign classes that implement the interface to that variable and call upon the methods declared in the interface to perform work. In the following example, a variable is declared to be of type TeamType. Using the same classes from Recipe 5-8, you can see that the class Team implements the TeamType interface. The variable that is created in the following example holds a reference to a new Team object.

Because the Team class implements the TeamType interface, the methods that are exposed in the interface can be used:

TeamType team = new Team();
team.setName("Juneau Royals");
team.setCity("Chicago");
System.out.println(team.getFullName());

The resulting output:

Juneau Royals – Chicago

How It Works

Interfaces are useful for many reasons. Two of the most important use cases for interfaces are conformity and abstraction. Interfaces define a model, and any class that implements the interface must conform to that model. Therefore, if there is a constant defined within the interface, it will automatically be available for use in the class. If there is a method defined within the interface, then the class must implement that method, unless a default implementation has been defined (see Recipe 5-7). Interfaces provide a nice way to allow classes to conform to a standard.

Interfaces hide unnecessary information from any class that does not need to see it. Any method that is defined within the interface is made public and accessible to any class. As demonstrated in the solution to this recipe, an object was created and declared to be the type of an interface. The interface in the example, TeamType, only includes a small subset of methods that are available within the Team object. Therefore, the only methods that are accessible to any class working against an object that has been declared to be of TeamType are the ones that are defined within the interface. The class using this interface type cannot see any of the other methods or constants, nor does it need to. Interfaces are a great way for hiding logic that does not need to be used by other classes. Another great side effect: A class that implements an interface can be changed and recompiled without affecting code that works against the interface. However, if an interface is changed, there could be an effect on any classes that implement it. Therefore, if the getFullName() method implementation changes, any class that is coded against the TeamType interface will not be affected because the interface is unchanged. The implementation will change behind the scenes, and any class working against the interface will just begin to use the new implementation without needing to know.

Image Note In some cases, alterations of existing classes can cause code to break. This is more often the case when working with libraries. For instance, suppose a class implements an interface that is updated with a new method signature. All classes that implement that interface must now be updated to include an implementation of the new method, which is sometimes impossible within library classes in order to maintain backward compatibility. This is the main reason for the inclusion of default methods in Java 8; see Recipe 5-7 for more details.

Finally, interfaces help to promote security. They hide implementation details of methods that are declared in an interface from any class that may call that method using the interface. As mentioned in the previous paragraph, if a class is calling the getFullName() method against theTeamType interface, it does not need to know the implementation details of that method as long as the result is returned as expected.

The Enterprise JavaBean (EJB) 3.0 model used interfaces for interacting with methods that performed database work. This model worked very well for hiding the details and logic that were not essential for use from other classes. Other frameworks use similar models, exposing functionality through Java interfaces. Interface use has proven to be a smart way to code software because it promotes reusability, flexibility, and security.

5-10. Making a Class Cloneable

Problem

You would like to enable a class to be cloned by another class.

Solution

Implement the Cloneable interface within the class that you want to clone; then call that object’s clone method to make a copy of it. The following code demonstrates how to make the Team class cloneable:

public class Team implements TeamType, Cloneable, Serializable {

private String name;
private String city;

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the city
*/
public String getCity() {
return city;
}

/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}

public String getFullName() {
return this.name + " - " + this.city;
}

/**
* Overrides Object's clone method to create a deep copy
*
* @return
*/
public Object clone() {

Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
oos.close();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
return obj;
}

/**
* Overrides Object's clone method to create a shallow copy
*
* @return
*/
public Object shallowCopyClone() {

try {
return super.clone();
} catch (CloneNotSupportedException ex) {
return null;
}
}

@Override
public boolean equals(Object obj) {

if (this == obj) {
return true;
}
if (obj instanceof Team) {
Team other = (Team) obj;
return other.getName().equals(this.getName())
&& other.getCity().equals(this.getCity());
} else {
return false;
}
}
}

Next, to make a deep copy of a Team object, the clone() method needs to be called against that object. To make a shallow copy of the object, the shallowCopyClone() method must be called. The following code demonstrates this technique:

Team team1 = new Team();
Team team2 = new Team();

team1.setCity("Boston");
team1.setName("Bandits");

team2.setCity("Chicago");
team2.setName("Wildcats");

Team team3 = team1;
Team team4 = (Team) team2.clone();

Team team5 = team1.shallowCopyClone();

System.out.println("Team 3:");
System.out.println(team3.getCity());
System.out.println(team3.getName());

System.out.println("Team 4:");
System.out.println(team4.getCity());
System.out.println(team4.getName());

// Teams move to different cities
team1.setCity("St. Louis");
team2.setCity("Orlando");

System.out.println("Team 3:");
System.out.println(team3.getCity());
System.out.println(team3.getName());

System.out.println("Team 4:");
System.out.println(team4.getCity());
System.out.println(team4.getName());

System.out.println("Team 5:");
System.out.println(team5.getCity());
System.out.println(team5.getName());

if (team1 == team3){
System.out.println("team1 and team3 are equal");
} else {
System.out.println("team1 and team3 are NOT equal");
}

if (team1 == team5){
System.out.println("team1 and team5 are equal");
} else {
System.out.println("team1 and team5 are NOT equal");
}

This code demonstrates how to make a clone of an object. The resulting output would be as follows.

Team 3:
Boston
Bandits
Team 4:
Chicago
Wildcats
Team 3:
St. Louis
Bandits
Team 4:
Chicago
Wildcats
Team 5:
Boston
Bandits
team1 and team3 are equal
team1 and team5 are NOT equal

How It Works

There are two different strategies that can be used to copy an object: shallow and deep copies. A shallow copy can be made that would copy the object without any of its contents or data. Rather, all the variables are passed by reference into the copied object. After a shallow copy of an object has been created, the objects within both the original object and its copy refer to the same data and memory. Thus, modifying the original object’s contents will also modify the copied object. By default, calling the super.clone() method against an object performs a shallow copy. TheshallowCopyClone() method in the solution to this recipe demonstrates this technique.

The second type of copy that can be made is known as a deep copy, which copies the object including all the contents. Therefore, each object refers to a different space in memory, and modifying one object will not affect the other. In the solution to this recipe, the difference between a deep and a shallow copy is demonstrated. First, team1 and team2 are created. Next, they are populated with some values. The team3 object is then set equal to the team1 object, and the team4 object is made a clone of the team2 object. When the values are changed within the team1object, they are also changed in the team3 object because both object’s contents refer to the same space in memory. This is an example of a shallow copy of an object. When the values are changed within the team2 object, they remain unchanged in the team4 object because each object has its own variables that refer to different spaces in memory. This is an example of a deep copy.

In order to make an exact copy of an object (deep copy), you must serialize the object. The base Object class implements the clone() method. By default, the Object class’s clone() method is protected. In order to make an object cloneable, it must implement theCloneable interface and override the default clone() method. You can make a deep copy of an object by serializing it through a series of steps, such as writing the object to an output stream and then reading it back via an input stream. The steps shown in the clone() method of the solution to this recipe do just that. The object is written to a ByteArrayOutputStream and then read using a ByteArrayInputStream. Once that has occurred, the object has been serialized, which creates the deep copy. The clone() method in the solution to this recipe has been overridden so that it creates a deep copy.

Once these steps have been followed and an object implements Cloneable as well as overrides the default object clone() method, it is possible to clone the object. In order to make a deep copy of an object, simply call that object’s overridden clone() method.

Team team4 = (Team) team2.clone();

Cloning objects is not very difficult, but a good understanding of the differences that can vary with object copies is important.

5-11. Comparing Objects

Problem

Your application requires the capability to compare two or more objects to see whether they are the same.

Solution 1

To determine whether the two object references point to the same object, make use of the == and != operators. The following solution demonstrates the comparison of two object references to determine whether they refer to the same object.

// Compare if two objects contain the same values
Team team1 = new Team();
Team team2 = new Team();

team1.setName("Jokers");
team1.setCity("Crazyville");

team2.setName("Jokers");
team2.setCity("Crazyville");

if (team1 == team2){
System.out.println("These object references refer to the same object.");
} else {
System.out.println("These object references do NOT refer to the same object.");
}

// Compare two objects to see if they refer to the same object
Team team3 = team1;
Team team4 = team1;

if (team3 == team4){
System.out.println("These object references refer to the same object.");
} else {
System.out.println("These object references do NOT refer to the same object.");
}

The results of running the code:

These object references do NOT refer to the same object.
These object references refer to the same object.

Solution 2

To determine whether the two objects contain the same values, use the equals() method. The object being compared must implement equals() and hashCode() in order for this solution to work properly. Following is the code for the Team class that overrides these two methods:

public class Team implements TeamType, Cloneable {

private List<Player> players;
private String name;
private String city;
// Used by the hashCode method for performance reasons
private volatile int cachedHashCode = 0;

/**
* @return the players
*/
public List<Player> getPlayers() {
return players;
}

/**
* @param players the players to set
*/
public void setPlayers(List<Player> players) {
this.players = players;
}

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the city
*/
public String getCity() {
return city;
}

/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}

public String getFullName() {
return this.name + " - " + this.city;
}

/**
* Overrides Object's clone method
*
* @return
*/
public Object clone() {

try {
return super.clone();
} catch (CloneNotSupportedException ex) {
return null;
}
}

@Override
public boolean equals(Object obj) {

if (this == obj) {
return true;
}
if (obj instanceof Team) {
Team other = (Team) obj;
return other.getName().equals(this.getName())
&& other.getCity().equals(this.getCity())
&& other.getPlayers().equals(this.getPlayers());
} else {
return false;
}

}

@Override
public int hashCode() {
int hashCode = cachedHashCode;
if (hashCode == 0) {
String concatStrings = name + city;
if (players.size() > 0) {
for (Player player : players) {
concatStrings = concatStrings
+ player.getFirstName()
+ player.getLastName()
+ player.getPosition()
+ String.valueOf(player.getStatus());

}
}
hashCode = concatStrings.hashCode();
}
return hashCode;
}
}

The following solution demonstrates the comparison of two objects that contain the same values.

// Compare if two objects contain the same values
Team team1 = new Team();
Team team2 = new Team();

// Build Player List
Player newPlayer = new Player("Josh", "Juneau");
playerList.add(0, newPlayer);
newPlayer = new Player("Jonathan", "Gennick");
playerList.add(1, newPlayer);
newPlayer = new Player("Joe", "Blow");
playerList.add(1, newPlayer);
newPlayer = new Player("John", "Smith");
playerList.add(1, newPlayer);
newPlayer = new Player("Paul", "Bunyan");
playerList.add(1, newPlayer);

team1.setName("Jokers");
team1.setCity("Crazyville");
team1.setPlayers(playerList);

team2.setName("Jokers");
team2.setCity("Crazyville");
team2.setPlayers(playerList);

if (team1.equals(team2)){
System.out.println("These object references contain the same values.");
} else {
System.out.println("These object references do NOT contain the same values.");
}

The results of running this code:

These object references contain the same values.

How It Works

The comparison operator (==) can be used to determine the equality of two objects. This equality does not pertain to the object values, but rather to the object references. Often an application is more concerned with the values of objects; in such cases, the equals() method is the preferred choice because it compares the values contained within the objects rather than the object references.

The comparison operator takes a look at the object reference and determines whether it points to the same object as the object reference that it is being compared against. If the two objects are equal, a Boolean true result will be returned; otherwise, a Boolean false result will be returned. In solution 1, the first comparison between the team1 object reference and the team2 object reference returns a false value because those two objects are separate in memory, even though they contain the same values. The second comparison in solution 1 between the team3object reference and the team4 object reference returns a true value because both of those references refer to the team1 object.

The equals() method can be used to test whether two objects contain the same values. In order to use the equals() method for comparison, the object that is being compared should override the Object class equals()and hashCode() methods. The equals() method should implement a comparison against the values contained within the object that would yield a true comparison result. The following code is an example of an overridden equals() method that has been placed into the Team object:

@Override
public boolean equals(Object obj) {

if (this == obj) {
return true;
}
if (obj instanceof Team) {
Team other = (Team) obj;
return other.getName().equals(this.getName())
&& other.getCity().equals(this.getCity())
&& other.getPlayers().equals(this.getPlayers());
} else {
return false;
}

}

As you can see, the overridden equals() method first checks to see whether the object that is passed as an argument is referencing the same object as the one that it is being compared against. If so, a true result is returned. If both objects are not referencing the same object in memory, the equals() method checks to see whether the fields are equal. In this case, any two Team objects that contain the same values within the name and city fields would be considered equal. Once the equals() method has been overridden, the comparison of the two objects can be performed, as demonstrated in solution 2 to this recipe.

The hashCode() method returns an int value that must consistently return the same integer. There are many ways in which to calculate the hashCode of an object. Perform a web search on the topic and you will find various techniques. One of the most basic ways to implement thehashCode() method is to concatenate all the object’s variables into string format and then return the resulting string’s hashCode(). It is a good idea to cache the value of the hashCode for later use because the initial calculation may take some time. The hashCode() method in solution 2 demonstrates this tactic.

Comparing Java objects can become confusing, considering that there are multiple ways to do it. If the comparison that you want to perform is against the object identity, use the comparison (==) operator. However, if you want to compare the values within the objects, or the state of the objects, then the equals() method is the way to go.

5-12. Extending the Functionality of a Class

Problem

One of your applications contains a class that you would like to use as a base for another class. You want your new class to contain the same functionality of this base class, but also include additional functionality.

Solution

Extend the functionality of the base class by using the extends keyword followed by the name of the class that you would like to extend. The following example shows two classes. The first class, named HockeyStick, represents a hockey stick object. It will be extended by the second class named WoodenStick. By doing so, the WoodenStick class will inherit all the properties and functionality contained within HockeyStick, with the exception of private variables and those that have the default access level. The WoodenStick class becomes a subclass ofHockeyStick. First, let’s take a look at the HockeyStick class , which contains the basic properties of a standard hockey stick:

public class HockeyStick {

public int length;
public boolean isCurved;
public String material;

public HockeyStick(int length, boolean isCurved, String material){
this.length = length;
this.isCurved = isCurved;
this.material = material;
}

public int getlength() {
return length;
}

public void setlength(int length) {
this.length = length;
}

public boolean isIsCurved() {
return isCurved;
}

public void setIsCurved(boolean isCurved) {
this.isCurved = isCurved;
}

public String getmaterial() {
return material;
}

public void setmaterial(String material) {
this.material = material;
}

}

Next, look at the subclass of HockeyStick: a class named WoodenStick.

public class WoodenStick extends HockeyStick {

public static final String material = "WOOD";
public int lie;
public int flex;

public WoodenStick(int length, boolean isCurved){
super(length, isCurved, material);
}

public WoodenStick(int length, boolean isCurved, int lie, int flex){
super(length, isCurved, material);
this.lie = lie;
this.flex = flex;
}
}

How It Works

Object inheritance is a fundamental technique in any object-oriented language. Inheriting from a base class adds value because it allows code to become reusable in multiple places. This helps to make code management much easier. If a change is made in the base class, it will automatically be inherited in the child. On the other hand, if you had duplicate functionality scattered throughout your application, one minor change could mean that you would have to change code in many places. Object inheritance also makes it easy to designate a base class to one or more subclasses so that each class can contain similar fields and functionality.

The Java language allows a class to extend only one other class. This differs in concept from other languages such as C++; which contain multiple inheritance. Although some look at single class inheritance as a hindrance to the language, it was designed that way to add safety and ease of use to the language. When a subclass contains multiple superclasses, confusion can ensue.

5-13. Defining a Template for Classes to Extend

Problem

You would like to define a template that can be used to generate objects containing similar functionality.

Solution

Define an abstract class that contains fields and functionality that can be used in other classes. The abstract class can also include unimplemented methods, called abstract methods, which will need to be implemented by a subclass of the abstract class. The following example demonstrates the concept of an abstract class. The abstract class in the example represents a team schedule, and it includes some basic field declarations and functionality that every team’s schedule will need to use. The Schedule class is then extended by the TeamScheduleclass, which will be used to implement specific functionality for each team. First, let’s take a look at the abstract Schedule class:

public abstract class Schedule {

public String scheduleYear;
public String teamName;

public List<Team> teams;

public List homeGames;
public List awayGames;

Map gameMap;

public Schedule(){}

public Schedule(String teamName){
this.teamName = teamName;
}

public Map obtainSchedule(){
if (gameMap == null){
gameMap = new HashMap();
}
return gameMap;
}

public void setGameDate(Team team, Date date){

obtainSchedule().put(team, date);
}

abstract void calculateDaysPlayed(int month);

}

Next, the TeamSchedule extends the functionality of the abstract class.

public class TeamSchedule extends Schedule {

public TeamSchedule(String teamName){
super(teamName);
}

@Override
void calculateDaysPlayed(int month) {

// Perform implementation here

throw new UnsupportedOperationException("Not supported yet.");
}

}

As you can see, the TeamSchedule class can use all the fields and methods that are contained within the abstract Schedule class. It also implements the abstract method that is contained within the Schedule class.

How It Works

Abstract classes are labeled as such, and they contain field declarations and methods that can be used within subclasses. What makes them different from a regular class? Abstract classes can contain abstract methods, which are method declarations with no implementation. The solution to this recipe contains an abstract method named calculateDaysPlayed(). Abstract classes may or may not contain abstract methods. They can contain fields and fully implemented methods as well. Abstract classes cannot be instantiated; other classes can only extend them. When a class extends an abstract class, it gains all the fields and functionality of the abstract class. However, any abstract methods that are declared within the abstract class must be implemented by the subclass.

You may wonder why the abstract class wouldn’t just contain the implementation of the method so that it was available for all its subclasses to use. If you think about the concept, it makes perfect sense. One type of object may perform a task differently from another. Using anabstract method forces the class that is extending the abstract class to implement it, but it allows the ability to customize how it is implemented.

5-14. Increasing Class Encapsulation

Problem

One of your classes requires the use of another class’s functionality. However, no other class requires the use of that same functionality. Rather than creating a separate class that includes this additional functionality, you’d like to generate an implementation that can only be used by the class that needs it, while placing the code in a logical location.

Solution

Create an inner class within the class that requires its functionality.

import java.util.ArrayList;
import java.util.List;

/**
* Inner class example. This example demonstrates how a team object could be
* built using an inner class object.
*
* @author juneau
*/
public class TeamInner {

private Player player;
private List<Player> playerList;
private int size = 4;

/**
* Inner class representing a Player object
*/
class Player {

private String firstName = null;
private String lastName = null;
private String position = null;
private int status = -1;

public Player() {
}

public Player(String position, int status) {
this.position = position;
this.status = status;
}

protected String playerStatus() {
String returnValue = null;

switch (getStatus()) {
case 0:
returnValue = "ACTIVE";
break;
case 1:
returnValue = "INACTIVE";
break;
case 2:
returnValue = "INJURY";
break;
default:
returnValue = "ON_BENCH";
break;
}

return returnValue;
}

public String playerString() {
return getFirstName() + " " + getLastName() + " - " + getPosition();
}

/**
* @return the firstName
*/
public String getFirstName() {
return firstName;
}

/**
* @param firstName the firstName to set
*/
public void setFirstName(String firstName) {
if (firstName.length() > 30) {
this.firstName = firstName.substring(0, 29);
} else {
this.firstName = firstName;
}
}

/**
* @return the lastName
*/
public String getLastName() {
return lastName;
}

/**
* @param lastName the lastName to set
*/
public void setLastName(String lastName) {
this.lastName = lastName;
}

/**
* @return the position
*/
public String getPosition() {
return position;
}

/**
* @param position the position to set
*/
public void setPosition(String position) {
this.position = position;
}

/**
* @return the status
*/
public int getStatus() {
return status;
}

/**
* @param status the status to set
*/
public void setStatus(int status) {
this.status = status;
}

@Override
public String toString(){
return this.firstName + " " + this.lastName + " - "+
this.position + ": " + this.playerStatus();
}
}

/**
* Inner class that constructs the Player objects and adds them to an array
* that was declared in the outer class;
*/
public TeamInner() {

final int ACTIVE = 0;

// In reality, this would probably read records from a database using
// a loop...but for this example we will manually enter the player data.
playerList = new ArrayList();
playerList.add(constructPlayer("Josh", "Juneau", "Right Wing", ACTIVE));
playerList.add(constructPlayer("Joe", "Blow", "Left Wing", ACTIVE));
playerList.add(constructPlayer("John", "Smith", "Center", ACTIVE));
playerList.add(constructPlayer("Bob","Coder", "Defense", ACTIVE));
playerList.add(constructPlayer("Jonathan", "Gennick", "Goalie", ACTIVE));
}

public Player constructPlayer(String first, String last, String position, int status){
Player player = new Player();
player.firstName = first;
player.lastName = last;
player.position = position;
player.status = status;
return player;
}

public List<Player> getPlayerList() {
return this.playerList;
}

public static void main(String[] args) {
TeamInner inner = new TeamInner();
System.out.println("Team Roster");
System.out.println("===========");
for(Player player:inner.getPlayerList()){
System.out.println(player.playerString());
}
}
}

The result of running this code is a listing of the players on the team.

Team Roster
===========
Josh Juneau - Right Wing
Joe Blow - Left Wing
John Smith - Center
Bob Coder - Defense
Jonathan Gennick - Goalie

How It Works

Sometimes it is important to encapsulate functionality within a single class. Other times it does not make sense to include a separate class for functionality that is only used within one other class. Imagine that you are developing a GUI and you need to use a class to support functionality for one button. If there is no reusable code within that button class, it does not make sense to create a separate class and expose that functionality for other classes to use. Instead, it makes sense to encapsulate that class inside of the class that requires the functionality. This philosophy is one use case for inner classes (also known as nested classes).

An inner class is a class that is contained within another class. The inner class can be made public, private, or protected just like any other class. It can contain the same functionality as a normal class; the only difference is that the inner class is contained within an enclosing class, otherwise referred to as an outer class. The solution to this recipe demonstrates this technique. The class TeamInner contains one inner class named Player. The Player class is a JavaBean class that represents a Player object. As you can see, the Player object has the capability to inherit functionality from its containing class, including its private fields. This is because inner classes contain an implicit reference to the outer class. It can also be accessed by the containing TeamInner class, as demonstrated within the constructPlayer() method:

public Player constructPlayer(String first, String last, String position, int status){
Player player = new Player();
player.firstName = first;
player.lastName = last;
player.position = position;
player.status = status;
return player;
}

Outer classes can instantiate an inner class as many times as needed. In the example, the constructPlayer() method could be called any number of times, instantiating a new instance of the inner class. However, when the outer class is instantiated, no instances of the inner class are instantiated.

Inner classes can reference outer class methods by referring to the outer class and then referring to the method that it wants to call. The following line of code demonstrates such a reference using the same objects that are represented in the solution to this recipe. Suppose that the Playerclass needed to obtain the player list from the outer class; you would write something similar to the following:

TeamInner.this.getPlayerList();

Although not very often used, classes other than the outside class can obtain access to a public inner class by using the following syntax:

TeamInner outerClass = new TeamInner();
outerClass.player = outerClass.new Player();

Static inner classes are a bit different, in that they cannot directly reference any instance variables or methods of its enclosing class. The following is an example of a static inner class.

public class StaticInnerExample {

static String hello = "Hello";

public static void sayHello(){
System.out.println(hello);
}

static class InnerExample {
String goodBye = "Good Bye";

public void sayGoodBye(){
System.out.println(this.goodBye);
}
}

public static void main (String[] args){
StaticInnerExample.sayHello();
StaticInnerExample.InnerExample inner =
new StaticInnerExample.InnerExample();
inner.sayGoodBye();
}
}

Inner classes help to provide encapsulation of logic. Furthermore, they allow inheritance of private fields, which is not possible using a standard class.

Summary

Java is an object-oriented language. To harness the capabilities of the language, one must learn how to become proficient with object-orientation. This chapter covered basics such as class creation and access modifiers. It also covered encapsulation, interfaces, and recipes to help developers take advantage of the power of object orientation. Lastly, this chapter included information on a new feature of Java 8 known as default methods.