返回介绍

Object-oriented programming II

发布于 2025-02-22 22:20:07 字数 30630 浏览 0 评论 0 收藏 0

In this chapter of the Java tutorial, we continue the description of the OOP.

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

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. A Java class can implement multiple interfaces. Multiple inheritance using the 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 the 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 Interface1 {

  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.

Polymorphism

The 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. (Wikipedia)

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.

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 the 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.

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.

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.

$ java com.zetcode.SNCTest 
This is a static nested class; x:5

This is the output of the com.zetcode.SNCTest program.

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.

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 the 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

Example output.

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.

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 object-oriented programming in Java.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文