In this chapter of the Java tutorial, we continue the description of the OOP in Java. We mention abstract classes and methods, interfaces, polymorphism, and various kinds of nested classes.
Java abstract classes and methods
Designing an application, we often find that our classes have a lot of common functionality. These commonalities can be extracted and put into parent classes. This way we reduce the size of our code and make our application more compact. We might find that the parent class is intangible, unreal entity — an idea. On a desk we have a pen, a book, a pencil, or a cup of tea. An Item might be considered as a parent class for all these things. The class will contain some common qualities of these items. For example an id, a weight, or a color. We can implement a getId()
method, but we cannot implement a getWeight()
or getColor()
methods in this class. An item has no weight or color. These methods can be implemented only in the subclasses of the Item class. For these situations, we have abstract methods and classes. An Item class is a candidate for an abstract class — a class that cannot be created and some or all of its methods cannot be implemented.
An abstract class or method is created using the abstract
keyword. Abstract classes cannot be instantiated but they can be subclassed. If a class contains at least one abstract method, it must be declared abstract too. Abstract methods cannot be implemented; they merely declare the methods' signatures. When we inherit from an abstract class, all abstract methods must be implemented by the derived class or the class must itself be abstract.
A single abstract class is subclassed by similar classes that have a lot in common (the implemented parts of the abstract class), but also have some differences (the abstract methods).
Abstract classes may have methods with full implementation and may also have defined member fields. So abstract classes may provide a partial implementation. Programmers often put some common functionality into abstract classes. And these abstract classes are later subclassed to provide more specific implementation. The common features are implemented in the abstract class, the differences are hinted by abstract methods. For example, the Qt graphics library has a QAbstractButton
which is the abstract base class of button widgets, providing functionality common to buttons. Buttons Q3Button
, QCheckBox
, QPushButton
, QRadioButton
, and QToolButton
inherit from this base abstract class and provide their specific functionality.
The static
, private
, and final
methods cannot be abstract, because these types of methods cannot be overridden by a subclass. Likewise, a final
class cannot have any abstract methods.
Formally put, abstract classes are used to enforce a protocol. A protocol is a set of operations which all implementing objects must support.
package com.zetcode; abstract class Drawing { protected int x = 0; protected int y = 0; public abstract double area(); public String getCoordinates() { return String.format("x: %d, y: %d", this.x, this.y); } } class Circle extends Drawing { private int r; public Circle(int x, int y, int r) { this.x = x; this.y = y; this.r = r; } @Override public double area() { return this.r * this.r * Math.PI; } @Override public String toString() { return String.format("Circle at x: %d, y: %d, radius: %d", this.x, this.y, this.r); } } public class AbstractClass { public static void main(String[] args) { Circle c = new Circle(12, 45, 22); System.out.println(c); System.out.format("Area of circle: %f%n", c.area()); System.out.println(c.getCoordinates()); } }
We have an abstract base Drawing
class. The class defines two member fields, defines one method and declares one method. One of the methods is abstract, the other one is fully implemented. The Drawing
class is abstract because we cannot draw it. We can draw a circle, a dot, or a square, but we cannot draw a "drawing". The Drawing
class has some common functionality to the objects that we can draw.
abstract class Drawing {
We use the abstract
keyword to define an abstract class.
public abstract double area();
An abstract method is also preceded with a abstract
keyword. A Drawing
class is an idea. It is unreal and we cannot implement the area()
method for it. This is the kind of situation where we use abstract methods. The method will be implemented in a more concrete entity like a circle.
class Circle extends Drawing {
A Circle
is a subclass of the Drawing
class. Therefore, it must implement the abstract area()
method.
@Override public double area() { return this.r * this.r * Math.PI; }
Here we are implementing the area()
method.
$ java com.zetcode.AbstractClass Circle at x: 12, y: 45, radius: 22 Area of circle: 1520.530844 x: 12, y: 45
We create a Circle
object and print its area and coordinates.
Java interfaces
A remote control is an interface between the viewer and the TV. It is an interface to this electronic device. Diplomatic protocol guides all activities in the diplomatic field. Rules of the road are rules that motorists, bikers, and pedestrians must follow. Interfaces in programming are analogous to the previous examples.
Interfaces are:
- APIs
- Contracts
Objects interact with the outside world with the methods they expose. The actual implementation is not important to the programmer, or it also might be secret. A company might sell a library and it does not want to disclose the actual implementation. A programmer might call a maximize()
method on a window of a GUI toolkit but knows nothing about how this method is implemented. From this point of view, interfaces are ways through which objects interact with the outside world, without exposing too much about their inner workings.
From the second point of view, interfaces are contracts. If agreed upon, they must be followed. They are used to design an architecture of an application. They help organize the code.
Interfaces are fully abstract types. They are declared using the interface
keyword. In Java, an interface is a reference type, similar to a class that can contain only constants, method signatures, and nested types. There are no method bodies. Interfaces cannot be instantiated—they can only be implemented by classes or extended by other interfaces. All interface members implicitly have public access. Interfaces cannot have fully implemented methods. A Java class may implement any number of interfaces. An interface scan also extend any number of interfaces. A class that implements an interface must implement all method signatures of an interface.
Interfaces are used to simulate multiple inheritance. A Java class can inherit only from one class but it can implement multiple interfaces. Multiple inheritance with interfaces is not about inheriting methods and variables, it is about inheriting ideas or contracts which are described by the interfaces.
The body of the interface contains abstract methods, but since all methods in an interface are, by definition, abstract, the abstract keyword is not required. Since an interface specifies a set of exposed behaviors, all methods are implicitly public. An interface can contain constant member declarations in addition to method declarations. All constant values defined in an interface are implicitly public
, static
, and final
. These modifiers can be omitted.
There is one important distinction between interfaces and abstract classes. Abstract classes provide partial implementation for classes that are related in the inheritance hierarchy. Interfaces on the other hand can be implemented by classes that are not related to each other. For example, we have two buttons. A classic button and a round button. Both inherit from an abstract button class that provides some common functionality to all buttons. Implementing classes are related, since all are buttons. Whereas classes Database
and SignIn
are not related to each other. We can apply an ILoggable
interface that would force them to create a method to do logging.
package com.zetcode; interface IInfo { void doInform(); } class Some implements IInfo { @Override public void doInform() { System.out.println("This is Some Class"); } } public class SimpleInterface { public static void main(String[] args) { Some sm = new Some(); sm.doInform(); } }
This is a simple Java program demonstrating an interface.
interface IInfo { void doInform(); }
This is an interface IInfo
. It has the doInform()
method signature.
class Some implements IInfo {
We implement the IInfo
interface. To implement a specific interface, we use the implements
keyword.
@Override public void doInform() { System.out.println("This is Some Class"); }
The class provides an implementation for the doInform()
method. The @Override
annotation tells the compiler that we are overriding a method.
Java does not allow to inherit from more than one class directly. It allows to implement multiple interfaces. The next example shows how a class can implement multiple interfaces.
package com.zetcode; interface Device { void switchOn(); void switchOff(); } interface Volume { void volumeUp(); void volumeDown(); } interface Pluggable { void plugIn(); void plugOff(); } class CellPhone implements Device, Volume, Pluggable { @Override public void switchOn() { System.out.println("Switching on"); } @Override public void switchOff() { System.out.println("Switching on"); } @Override public void volumeUp() { System.out.println("Volume up"); } @Override public void volumeDown() { System.out.println("Volume down"); } @Override public void plugIn() { System.out.println("Plugging in"); } @Override public void plugOff() { System.out.println("Plugging off"); } } public class MultipleInterfaces { public static void main(String[] args) { CellPhone cp = new CellPhone(); cp.switchOn(); cp.volumeUp(); cp.plugIn(); } }
We have a CellPhone
class that inherits from three interfaces.
class CellPhone implements Device, Volume, Pluggable {
The class implements all three interfaces which are divided by a comma. The CellPhone
class must implement all method signatures from all three interfaces.
$ java com.zetcode.MultipleInterfaces Switching on Volume up Plugging in
Running the program we get this output.
The next example shows how interfaces can form a hierarchy. Interfaces can inherit from other interfaces using the extends
keyword.
package com.zetcode; interface IInfo { void doInform(); } interface IVersion { void getVersion(); } interface ILog extends IInfo, IVersion { void doLog(); } class DBConnect implements ILog { @Override public void doInform() { System.out.println("This is DBConnect class"); } @Override public void getVersion() { System.out.println("Version 1.02"); } @Override public void doLog() { System.out.println("Logging"); } public void connect() { System.out.println("Connecting to the database"); } } public class InterfaceHierarchy { public static void main(String[] args) { DBConnect db = new DBConnect(); db.doInform(); db.getVersion(); db.doLog(); db.connect(); } }
We define three interfaces. The interfaces are organized in a hierarchy.
interface ILog extends IInfo, IVersion {
The ILog
interface inherits from two interfaces.
class DBConnect implements ILog {
The DBConnect
class implements the ILog
interface. Therefore it must implement the methods of all three interfaces.
@Override public void doInform() { System.out.println("This is DBConnect class"); }
The DBConnect
class implements the doInform()
method. This method was inherited by the ILog
interface which the class implements.
$ java com.zetcode.InterfaceHierarchy This is DBConnect class Version 1.02 Logging Connecting to the database
This is the example output.
Java polymorphism
is the process of using an operator or function in different ways for different data input. In practical terms, polymorphism means that if class B inherits from class A, it doesn't have to inherit everything about class A; it can do some of the things that class A does differently.
In general, polymorphism is the ability to appear in different forms. Technically, it is the ability to redefine methods for derived classes. Polymorphism is concerned with the application of specific implementations to an interface or a more generic base class.
Simply put, polymorphism is the ability to redefine methods for derived classes.
package com.zetcode; abstract class Shape { protected int x; protected int y; public abstract int area(); } class Rectangle extends Shape { public Rectangle(int x, int y) { this.x = x; this.y = y; } @Override public int area() { return this.x * this.y; } } class Square extends Shape { public Square(int x) { this.x = x; } @Override public int area() { return this.x * this.x; } } public class Polymorphism { public static void main(String[] args) { Shape[] shapes = { new Square(5), new Rectangle(9, 4), new Square(12) }; for (Shape shape : shapes) { System.out.println(shape.area()); } } }
In the above program, we have an abstract Shape
class. This class morphs into two descendant classes: Rectangle
and Square
. Both provide their own implementation of the area()
method. Polymorphism brings flexibility and scalability to the OOP systems.
@Override public int area() { return this.x * this.y; } ... @Override public int area() { return this.x * this.x; }
The Rectangle
and Square
classes have their own implementations of the area()
method.
Shape[] shapes = { new Square(5), new Rectangle(9, 4), new Square(12) };
We create an array of three shapes.
for (Shape shape : shapes) { System.out.println(shape.area()); }
We go through each shape and call the area()
method on it. The compiler calls the correct method for each shape. This is the essence of polymorphism.
Java nested classes
It is possible to define a class within another class. Such class is called a nested class in Java terminology. A class that is not a nested class is called a top-level class.
Java has four types of nested classes:
- Static nested classes
- Inner classes
- Local classes
- Anonymous classes
Using nested classes may increase the readability of the code and improve the organization of the code. Inner classes are often used as callbacks in GUI. For example in Java Swing toolkit.
Java static nested classes
A static nested class is a nested class that can be created without the instance of the enclosing class. It has access to the static variables and methods of the enclosing class.
package com.zetcode; public class SNCTest { private static int x = 5; static class Nested { @Override public String toString() { return "This is a static nested class; x:" + x; } } public static void main(String[] args) { SNCTest.Nested sn = new SNCTest.Nested(); System.out.println(sn); } }
The example presents a static nested class.
private static int x = 5;
This is a private static variable of the SNCTest
class. It can be accessed by a static nested class.
static class Nested { @Override public String toString() { return "This is a static nested class; x:" + x; } }
A static nested class is defined. It has one method which prints a message and refers to the static x
variable.
SNCTest.Nested sn = new SNCTest.Nested();
The dot operator is used to refer to the nested class.
$ java com.zetcode.SNCTest This is a static nested class; x:5
This is the output of the com.zetcode.SNCTest
program.
Java inner classes
An instance of a normal or top-level class can exist on its own. By contrast, an instance of an inner class cannot be instantiated without being bound to a top-level class. Inner classes are also called member classes. They belong to the instance of the enclosing class. Inner classes have access to the members of the enclosing class.
package com.zetcode; public class InnerClassTest { private int x = 5; class Inner { @Override public String toString() { return "This is Inner class; x:" + x; } } public static void main(String[] args) { InnerClassTest nc = new InnerClassTest(); InnerClassTest.Inner inner = nc.new Inner(); System.out.println(inner); } }
A nested class is defined in the InnerClassTest
class. It has access to the member x
variable.
class Inner { @Override public String toString() { return "This is Inner class; x:" + x; } }
An Inner
class is defined in the body of the InnerClassTest
class.
InnerClassTest nc = new InnerClassTest();
First, we need to create an instance of the top-level class. Inner classes cannot exist without an instance of the enclosing class.
InnerClassTest.Inner inner = nc.new Inner();
Once we have the top-level class instantiated, we can create the instance of the inner class.
$ java com.zetcode.InnerClassTest This is Inner class; x:5
This is the output of the com.zetcode.InnerClassTest
program.
Java variable shadowing
If a variable in the inner scope has the same name as the variable in the outer scope then it is shadowing it. It is still possible to refer to the variable in the outer scope.
package com.zetcode; public class Shadowing { private int x = 0; class Inner { private int x = 5; void method1(int x) { System.out.println(x); System.out.println(this.x); System.out.println(Shadowing.this.x); } } public static void main(String[] args) { Shadowing sh = new Shadowing(); Shadowing.Inner si = sh.new Inner(); si.method1(10); } }
We define an x
variable in the top-level class, in the inner class, and inside a method.
System.out.println(x);
This line refers to the x
variable defined in the local scope of the method.
System.out.println(this.x);
Using this
keyword, we refer to the x
variable defined in the Inner
class.
System.out.println(Shadowing.this.x);
Here we refer to the x
variable of the Shadowing
top-level class.
$ java com.zetcode.Shadowing 10 5 0
This is example output.
Java local classes
A local class is a special case of an inner class. Local classes are classes that are defined in a block. (A block is a group of zero or more statements between braces.) A local class has access to the members of its enclosing class. In addition, a local class has access to local variables if they are declared final
. The reason for this is technical. The lifetime of an instance of a local class can be much longer than the execution of the method in which the class is defined. To solve this, the local variables are copied into the local class. To ensure that they are later not changed, they have to be declared final
.
Local classes cannot be public
, private
, protected
, or static
. They are not allowed for local variable declarations or local class declarations. Except for constants that are declared static
and final
, local classes cannot contain static fields, methods, or classes.
package com.zetcode; public class LocalClassTest { public static void main(String[] args) { final int x = 5; class Local { @Override public String toString() { return "This is Local class; x:" + x; } } Local loc = new Local(); System.out.println(loc); } }
A local class is defined in the body of the main()
method.
@Override public String toString() { return "This is Local class; x:" + x; }
A local class can access local variables if they are declared final.
Java anonymous classes
Anonymous classes are local classes that do not have a name. They enable us to declare and instantiate a class at the same time. We can use anonymous classes if we want to use the class only once. An anonymous class is defined and instantiated in a single expression. Anonymous inner classes are also used where the event handling code is only used by one component and therefore does not need a named reference.
An anonymous class must implement an interface or inherit from a class. But the implements
and extends
keywords are not used. If the name following the new keyword is the name of a class, the anonymous class is a subclass of the named class. If the name following new specifies an interface, the anonymous class implements that interface and extends the Object
.
Since an anonymous class has no name, it is not possible to define a constructor for an anonymous class. Inside the body of an anonymous class we cannot define any statements; only methods or members.
package com.zetcode; public class AnonymousClass { interface Message { public void send(); } public void createMessage() { Message msg = new Message() { @Override public void send() { System.out.println("This is a message"); } }; msg.send(); } public static void main(String[] args) { AnonymousClass ac = new AnonymousClass(); ac.createMessage(); } }
In this code example, we create an anonymous class.
interface Message { public void send(); }
An anonymous class must be either a subclass or must implement an interface. Our anonymous class will implement a Message
interface. Otherwise, the type would not be recognized by the compiler.
public void createMessage() { Message msg = new Message() { @Override public void send() { System.out.println("This is a message"); } }; msg.send(); }
An anonymous class is a local class, hence it is defined in the body of a method. An anonymous class is defined in an expression; therefore, the enclosing right bracket is followed by a semicolon.
In this part of the Java tutorial, we continued covering the object-oriented programming in Java.