Java Object-oriented programming II

xingyun86 2021-5-30 827

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 Q3ButtonQCheckBoxQPushButtonQRadioButton, and QToolButton inherit from this base abstract class and provide their specific functionality.

The staticprivate, 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.

AbstractClass.java
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 {

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 publicstatic, 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.

SimpleInterface.java
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.

MultipleInterfaces.java
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.

InterfaceHierarchy.java
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.

Polymorphism.java
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.

SNCTest.java
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.

InnerClassTest.java
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.

Shadowing.java
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 publicprivateprotected, 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.

LocalClassTest.java
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.

AnonymousClass.java
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.


×
打赏作者
最新回复 (0)
只看楼主
全部楼主
返回