This presentation introduces the abstraction and encapsulation capabilities offered by Java.

Generally,

Examples of these concepts will be given through an application that performs matrix algebra.
The general idea of an interface comes from hardware. For example: The idea of an interface in software, in particular object-oriented software, can be found in Applications Programming Interfaces, or APIs.

APIs specify how classes can be used, particularly what their public methods are and how they should be called.

A Java type is specified by indicating the kinds of operations that can be performed on values of the type.

Here are some operations we would expect to be able to perform on a matrix, and which therefore should be part of a matrix API:

Java has the concept of an interface built into the language. Interfaces describe methods by giving their signatures (name, return type and parameters types) but not their bodies.

A Java interface is a little like a C++ class with only pure virtual methods. However, interfaces do not have constructors, and they do not define instance fields.

Interfaces are intended to be implemented by classes with constructors and instance fields. When a class implements an interface, it is obligated to provide method bodies (also called implementations) of all methods in the interface (with the exception of abstract classes, discussed later).

Below is a Matrix interface type definition that reflects how we would like to use matrices. Note:

As of Java 8, interface definitions can include default methods, i.e. methods with implementations. When a method implementation makes use of methods whose own implementations are provided by another class, it is called a "template method."
Here are two things we might expect a matrix type to do for us: In the examples that follow, note the use of the "default" keyword and the method bodies.
The code below shows default methods that support matrix bounds checking.
The code below shows a default method that clears a matrix. It is also a template method.

The default methods have been added to the end of the interface definition:

The Unified Modeling Language (UML) can be used to represent interfaces graphically.

You will learn how to use UML in a lab exercise, but below is a UML representation of the Matrix interface as currently defined.

Also shown (using the dotted arrow) is the interface's dependence on the MatrixException class.

Interfaces are intended to be implemented by classes. In our example, we are going to provide two implementations: one based on arrays (the class ArrayImplementation), and one based on lists (the class ArrayListImplementation).

If each of these classes were defined from scratch, they would have code in common; for example, the getNumRows and getNumColumns would be the same.

We will instead define a class that partially implements the Matrix interface, and from which both ArrayImplementation and ArrayListImplementation can inherit.

In Java, such a class is called abstract. We will call this class AbstractMatrix.

The AbstractMatrix class will implement most of the abstract (non-default) methods in the Matrix interface, but it will not provide an underlying representation of matrix elements.

AbstractMatrix has instance fields to store the number of rows and columns in the matrix, and implementations of the following methods:

The get and set methods are not implemented, since they must be provided by an underlying data representation class. This is why this class is abstract.

Also note that equals, add, and multiply are intended to be written by students, so their implementations are not shown here.

Below is the source for AbstractMatrix. Note the use of the "abstract" keyword in the class header.
When any class, abstract or not, implements an interface, it is shown in UML with the dotted line ending with a triangle, as shown below.

Note that the class icon in UML provides a location for showing attributes (instance fields), along with the new methods defined.

At this point, it is worth noting that we have not yet considered how actual elements of a matrix are to be represented. That is, we have not decided on a data representation for matrices.

Yet, through the Matrix interface and the AbstractMatrix abstract class, we have provided usable code that stores matrix dimension information and implements the following operations:

We have managed to do this because we have strictly separated the details of how matrices are represented from how matrices are used.

That is, we have enforced the principle of data abstraction.

The only matrix API operations we have described and not implemented are those to get and set actual array elements, because they require a data representation.

Of course, to have code that we can run, we must come up with a data representation. For this, we introduce the concept of a concrete class.

A class that is not abstract is concrete. If it implements an interface or extends a class that does, it completes the full implementation of the interface.

In our example, we have two concrete classes that complete the implementation of the Matrix interface by subclassing (extending) the AbstractMatrix class: ArrayImplementation and ArrayListImplementation. The first is complete, while the second is intended to be completed by students.

Shown below is the UML depiction of these classes extending the AbstractMatrix class, using the solid line ending in a triangle.

See the menu to the left for their source code.

An important operation that so far is not part of our matrix API is creating a new matrix given the number of rows and columns.

We will consider two approaches to remedying this.

One way is to add a create method to the API along with get, set, add, etc., like this:

In fact, now that we have a data representation for matrices and constructors for them, we could use a default method:

The problem with this approach is that in order to create a new matrix, we would need an already created matrix object to do it. For example, suppose m1 is an existing matrix. Then we can create m2 like this:

This has two problems:

The solution is to make create a static method, so that it can be created like this:

Since Java 8, we can introduce static methods in interfaces simply by replacing the "default" keyword with "static":

Not only is this more natural, it also allows us to easily change our data representation choice without affecting any other code in the API. Note how the following "swaps out" the array implementation for the array list implementation:

The inclusion of the static create method introduces dependencies on the concrete implementation files, as shown below.

While in general dependencies in UML models should be minimized, in this case their use does not impede the benefits of polymorphism, as described next.

One of the dictates of effective object-oriented design is: Code to interfaces. That is, whenever possible define interface types for your program variables rather than class types.

The reason is that code using interface types for variables is easier to extend and modify.

Without the Matrix interface type and AbstractMatrix abstract class, we would have the following disadvantages:

An example is found in the MatrixTest class used in our application, whose code is shown in the menu to the left: The fact that we can change the Matrix interface's create method from one implementation to another without having to change any MatrixTest code is an effect of polymorphism:
Here are all the files from this presentation collected together.