Java. Object Oriented Programming with Interfaces

Lecture



Introduction

"In the narrow sense of the word, Java is an object-oriented language, reminiscent of C ++, but simpler to learn and use. In a broader sense, Java is a whole programming technology originally designed to integrate with a Web service. ... Java environment should be as mobile as possible, ideally completely platform independent. " Java as the center of the archipelago. Alexander Taranov, Vladimir Tsishevsky

This quote is typical of Java articles. Everything said in it is true. But not all.

Java is not only "C ++ without pointers" and not only "C ++ for the Internet". Java is the new generation object-oriented language.

Awareness of this fact required me to revise the stereotypes that have developed during programming in C ++. This process I plan to display in a series of articles "OOP in Java."

This article discusses one of the distinguishing features of Java as an object-oriented programming language - the concept of an interface. In my opinion, the concept of the interface is one of the most important innovations of the language, not fully realized yet. Interfaces are waiting for their researchers.

Proposed next topics - multiple inheritance, dynamic types, generalization and classification, mapping to RDBMS.

The article focuses more on Java philosophy than on practical programming guidelines. It is assumed that the reader is familiar with OOP in C ++ or Java.

The first question I had when meeting with Java is - Why do we need interfaces? It would seem to be quite enough ordinary and abstract classes in the style of C ++. To disguise the lack of multiple inheritance? Funny

After some amount of thinking, and especially after repeatedly reading the book of Bruce Ekkel mentioned above, the question transformed: Why do we need classes in Java?

The usual answer choices: the class introduces a new type, the class summarizes ....

Type declaration

The essence of things then you will understand when you correctly name it.

A new type is introduced by the interface specification.

In C ++, a class implicitly defines an interface. And because of this, simultaneously declares a type. In this case, a single interface is associated with a single implementation. Multiple inheritance and abstract classes in C ++ are first and foremost an attempt to circumvent hard determinism.

There is no such limitation in Java. Any interface (type) can have many implementations. Any class can implement many interfaces.

As an example, let's try to declare our own type "number". For brevity, we restrict ourselves to the operations of addition and multiplication.

 

  / * INumber.java --------------------------------------------- --------- (8 < 
   * 
   * Declaration of type INumber and program fragment using this type. 
   * 
   * ------------------------------------------------- ------------------- * / 

  interface INumber 
  { 
    public void setValue (String s); 
    public INumber add (INumber n); 
    public INumber mul (INumber n); 
    public String toString (); 
  } 

  class CalcNumber 
  { 
    void calculation (INumber n1, INumber n2, INumber n3) 
    { 
      INumber xx; 
      xx = n2; 
  // If you comment out the previous line, the compiler will generate an error: 
  // variable xx might not have been initialized 
      xx.setValue ("5.3"); 
      System.out.println ("xx =" + xx.toString ()); 

      n1.setValue ("21"); 
      n2.setValue ("37.6"); 
      System.out.println ("n1 =" + n1.toString ()); 
      System.out.println ("n2 =" + n2.toString ()); 
      System.out.println ("n3 =" + n3.toString ()); 

      System.out.println ("(n1 + n2) * n3 =" + n1.add (n2) .mul (n3) .toString ()); 
      n1.setValue ("21"); 
      System.out.println ("(n2 + n1) * n3 =" + n2.add (n1) .mul (n3) .toString ()); 
      n2.setValue ("37.6"); 
      System.out.println ("n1 * (n2 + n3) =" + n1.mul (n2.add (n3)). ToString ()); 
      n1.setValue ("21"); 
      n2.setValue ("37.6"); 
      System.out.println ("n3 * (n1 + n2) =" + n3.mul (n1.add (n2)). ToString ()); 
    } 
  } // (8 < 
 

From the above example shows:

  • The interface allows you to declare a type. In the given example, variables and parameters of the INumber type are declared, actions on them are described. Compilation is done without errors.
  • The type implementation is passed through the object. Objects n1, n2 and n3 are passed through parameters. Thus, the compiler is informed that the objects are initialized somewhere outside this module. It's enough. Classes are not needed yet.
  • We cannot initialize an object in the given module, since for this you need to have an implementation.

The gap between the data type declaration and its implementation is not new. For example, specification C states that the implementation of base types depends on the platform. And the implementation of floating numbers, even on one platform, has always depended on the presence of a coprocessor.

Add two options for implementation.

 

  / * DblNumber.java --------------------------------------------- ------- (8 < 
   * 
   * Implementation of type INumber through double. 
   *  
   * ------------------------------------------------- ------------------- * / 

  class DblNumber implements INumber 
  { 
    double d; 
    public DblNumber (double ip) 
    { 
      d = ip; 
    } 

    public void setValue (String s) 
    { 
      d = (new Double (s)). doubleValue (); 
    } 

    public INumber add (INumber n) 
    { 
      d + = (new Double (n.toString ())). doubleValue (); 
      return this; 
    } 

    public INumber mul (INumber n) 
    { 
      d * = (new Double (n.toString ())). doubleValue (); 
      return this; 
    } 

    public String toString () 
    { 
      return (new Double (d)). toString (); 
    } 
  } // (8 < 


  / * IntNumber.java --------------------------------------------- ------- (8 < 
   * 
   * Implementation of type INumber via int. 
   * 
   * ------------------------------------------------- ------------------- * / 

  class IntNumber implements INumber 
  { 
    int i; 
    public intNumber (int v) 
    { 
      i = v; 
    } 

    public void setValue (String s) 
    { 
      String sw = s; 
      int l = sw.indexOf ('.'); 
      if (l> 0) 
        sw = sw.substring (0, l); 
      i = (new Integer (sw)). intValue (); 
    } 

    public INumber add (INumber n) 
    { 
      String sw = n.toString (); 
      int l = sw.indexOf ('.'); 
      if (l> 0) 
        sw = sw.substring (0, l); 
      i + = (new Integer (sw)). intValue (); 
      return this; 
    } 

    public INumber mul (INumber n) 
    { 
      String sw = n.toString (); 
      int l = sw.indexOf ('.'); 
      if (l> 0) 
        sw = sw.substring (0, l); 
      i * = (new Integer (sw)). intValue (); 
      return this; 
    } 

    public String toString () 
    { 
      return (new Integer (i)). toString (); 
    } 
  } // (8 < 
 
Check the result:
 

  / * TestNumber.java --------------------------------------------- ------ (8 < 
   * 
   * INumber type testing. 
   *  
   * ------------------------------------------------- ------------------- * / 

  public class TestNumber 
  { 
    public static void main (String [] args) { 
      INumber i1 = new IntNumber (22); 
      INumber i2 = new DblNumber (11.2); 
      INumber i3 = new DblNumber (3.4); 
      CalcNumber cn = new CalcNumber (); 
      cn.calculation (i1, i2, i3); 
    } 
  } // (8 < 

 

The result of the test program:

  xx = 5.3
 n1 = 21
 n2 = 37.6
 n3 = 3.4
 (n1 + n2) * n3 = 174
 (n2 + n1) * n3 = 199.24
 n1 * (n2 + n3) = 861
 n3 * (n1 + n2) = 197.2

Note: the implementation is passed through the object. The class is needed to generate the object carrying the implementation. But not necessarily, as we shall see later.

It is interesting to note that the result of the operation on INumber depends on the sequence of variables used. The effect arises because in the type specification we omitted the properties that are important for numbers: the accuracy and the range of acceptable values. As a result, they are implicitly taken from the base type used in the implementation. In this case, just add a method
setFormat(maxValue, minValue, decimal) .

Type implementation

- Do I need to know the aspirin formula to cure a headache?
- Not! Enough to have money in your pocket.

In the previous example, we saw that the implementation is passed through an object. Consequently, the object is packed with all the necessary information on the implementation of the interface. If the behavior is determined by the interface, and the implementation is packaged in an object, then why do we need a class? - Classes are needed for implementation inheritance and code reuse. If reuse is not required, then the class is not needed.

In the following example, there is only one class - to run the application. The actual application logic is implemented without the use of classes!

 

  / * TestAnimal.java --------------------------------------------- ------ (8 < 
   * 
   * Sample classless implementation 
   * 
   * ------------------------------------------------- ------------------- * / 

  import java.util.ArrayList; 

  interface Animal 
  { 
    void giveSignals (); 
    void goHome (); 
    String getTitle (); 
    String getNick (); 
  } 

  interface Command 
  { 
    void exeCommand (Animal an); 
  } 

  interface ranch 
  { 
    void add (Animal an); 
    void visitAll (Command cmd); 
  } 

  public class TestAnimal 
  { 
    public static void main (String [] args) 
    { 
       Ranch myRanch = new Ranch () 
       { 
         private ArrayList ranchAnimals = new ArrayList (); 
         public void add (Animal a) 
         { 
           ranchAnimals.add (a); 
         } 
      
         public void visitAll (Command cmd) 
         { 
           for (int i = 0; i <ranchAnimals.size (); i ++) 
             cmd.exeCommand ((Animal) ranchAnimals.get (i)); 
         } 
       };  // end of new Ranch () 

       // add animals 
       myRanch.add (new Animal () // dog 
          { 
            public void giveSignals () 
            { 
              System.out.println ("Gav-gav"); 
            } 
         
            public void goHome () 
            { 
              System.out.println ("Runs to the booth"); 
            } 
         
            public String getTitle () 
            { 
              return new String ("dog"); 
            } 
         
            public String getNick () 
            { 
              return new String ("Black"); 
            } 
          });  // end of add new Animal dog 

       myRanch.add (new Animal () // sheep 
          { 
            public void giveSignals () 
            { 
              System.out.println ("Bee"); 
            } 
         
            public void goHome () 
            { 
              System.out.println ("Goes to the pen"); 
            } 
         
            public String getTitle () 
            { 
              return new String ("sheep"); 
            } 
         
            public String getNick () 
            { 
              return new String (""); 
            } 
          });  // end of add new Animal sheep 

       myRanch.add (new Animal () // another sheep 
          { 
            public void giveSignals () 
            { 
              System.out.println ("Bee"); 
            } 
         
            public void goHome () 
            { 
              System.out.println ("Goes to the pen"); 
            } 
         
            public String getTitle () 
            { 
              return new String ("sheep"); 
            } 
         
            public String getNick () 
            { 
              return new String (""); 
            } 
          });  // end of add new Animal another sheep 

       // gives signals 
       System.out.println ("\ n <<<<<<< All voted >>>>>>>>> \ n"); 
       myRanch.visitAll (new Command () 
          { 
            public void exeCommand (Animal a) 
            { 
              System.out.print (a.getTitle () + "" + a.getNick () + "says:"); 
              a.giveSignals (); 
            } 
          }); 

  // go to Home 
       System.out.println ("\ n <<<<<<< All home! >>>>>>>>> \ n"); 
       myRanch.visitAll (new Command () 
          { 
            public void exeCommand (Animal a) 
            { 
              System.out.print (a.getTitle () + "" + a.getNick () + "goes home:"); 
              a.goHome (); 
            } 
          }); 

    } 
  } // (8 < 
 

Using the class Sheep would reduce the text of the program. The introduction of this class provides no other advantages. For other objects, the definition of the corresponding classes does not give anything.

The result of the program:

  <<<<<<< All cast a vote >>>>>>>>>

 Black's dog says: Gav-gav
 the sheep says: bee
 the sheep says: bee

 <<<<<<< All home!  >>>>>>>>>

 Black's dog goes home: Runs to the booth
 a sheep goes home: goes to the fold
 a sheep goes home: goes to the fold

Someone will say that in the above example anonymous classes are used and will be right.

But what is an anonymous class? The Java specification says: the anonymous class declaration is automatically extracted by the compiler from the class instance creation expression. Those. The authors of the language used the principle of the teapot and led the task of creating a "self-defined" object to the already solved one. In other words, a class is usually declared first, and then an instance is generated. With an anonymous class, the opposite is true - the instance is first described, and then the class is fitted to it. Reengineering is called. :)

We can say that an anonymous class is needed in order to legitimize the existence of the created object.

That is, in this case, the class is a technical tool for packaging implementation. A small, relatively autonomous piece of the program (data + code). And outside the place where the packaging takes place, nobody needs it.

Another thing, if this piece is repeated regularly. Then it makes sense to make it available from different parts of the program. Thus, the class is only needed for reuse. In addition, in a large program, highlighting code into classes improves its readability.

Really anonymous classes I came across infrequently. But it's not about where the implementation is packed — in a regular class or in an anonymous one. It is important to understand the difference between the role of the class and the role of the interface.

Type inheritance

Gruzdev called himself get in the body.

Type inheritance and polymorphism are provided by interface inheritance and nothing else.

A simple example:

 

  / * TestShips.java --------------------------------------------- ------- (8 < 
   * 
   * Inheritance of interfaces and polymorphism 
   * 
   * ------------------------------------------------- ------------------- * / 

  import java.util.ArrayList; 

  interface Ship 
  { 
    void runTo (String s); 
  } 

  interface WarShip extends Ship 
  { 
    void bombard (); 
  } 

  interface Transport extends Ship 
  { 
    void loadTroops (int n); 
    void landTroops (); 
  } 

  public class TestShips 
  { 
    public static void main (String [] args) 
    { 
       ArrayList ships = new ArrayList (); 

       for (int i = 0; i <3; i ++) 
         ships.add (new Transport () 
           { 
             private int troopers; 
             public void runTo (String s) {System.out.println ("Transport goes to" + s + ".");  } 
             public void loadTroops (int n) {troopers = n;  } 
             public void landTroops () 
             {System.out.println ((new Integer (troopers)). ToString () + "squads landed.");  } 
           } 
         ); 

       for (int i = 0; i <2; i ++) 
         ships.add (new WarShip () 
           { 
             public void runTo (String s) {System.out.println ("Ship sent to" + s + ".");  } 
             public void bombard () {System.out.println ("A ship is bombarding a target.");  } 
           } 
         ); 

       for (int i = 0; i <3; i ++) 
        ((Transport) ships. Get (i)). LoadTroops (i + 5); 

       for (int i = 0; i <ships.size (); i ++) 
        ((Ship) ships. Get (i)). RunTo ("Enemy Port"); 

       for (int i = 0; i <3; i ++) 
        ((Transport) ships.get (i)). LandTroops (); 

       for (int i = 3; i <ships.size (); i ++) 
        ((WarShip) ships.get (i)). Bombard (); 

  // Run-time error: java.lang.ClassCastException 
  // ((Transport) ships.get (4)). LandTroops (); 

  // Run-time error: java.lang.ClassCastException 
  // ((WarShip) ships. Get (1)). Bombard (); 

  // Compile-time error: cannot resolve symbol 
  // ((Ship) ships. Get (1)). LandTroops (); 
  // ((Ship) ships. Get (4)). Bombard (); 
    } 
  } // (8 < 
 

The result of the program:

  Transport goes to Enemy Port.
 Transport goes to Enemy Port.
 Transport goes to Enemy Port.
 The ship is heading to Enemy Port.
 The ship is heading to Enemy Port.
 5 squads landed.
 6 troops paratrooped.
 7 squads landed.
 The ship is bombarding the target.
 The ship is bombarding the target.

The concept of interfaces adds a second dimension to polymorphism:

  • C ++ style hierarchical polymorphism, based on casting to the base type of classes and / or interfaces (see TestShips);
  • Instance polymorphism based on different implementations of the same interface (see INumber).

Inheritance has two aspects:

  • "to be like (outwardly)" - type inheritance, behavior;
  • "to be arranged as" - implementation inheritance.

Implementation inheritance does not mean type inheritance! In practice, this does not occur, because in C ++ and in Java it is impossible to inherit an implementation without interface inheritance. In C ++, the interface and class are inseparable. In Java, the interface can be separated from the class, but the class cannot be separated from the interface.

In C ++ and in Java, a collection of public (public) methods implicitly forms the interface of this class. Because of this, class inheritance automatically means both implementation inheritance and interface (type) inheritance. Obviously, the inheritance of the data structure and program code does not determine the type of descendant. For example, abstract methods are part of the interface and are not part of the implementation. If it were possible to exclude them from inheritance, then we would receive the inheritance of the implementation without saving the type.

Note that there is no implementation inheritance in DblNumber and IntNumber. Therefore, the class hierarchy is not used.

Generalization

What remains of the class? - Generalization.
More precisely, a generalization of the implementation.
And to be honest to the end - the organization of reusable code.

Possible reuse:

  • source code (attributes);
  • executable code (methods).

Classes provide two dimensions of reuse:

  • classification - an instance (object) uses the implementation of the class;
  • generalization - classes inherit the implementation of the parent classes.

Thus, the true purpose of the class is the packaging of reusable code in accordance with the principles of object-oriented technology.

avatar
22.3.2020 13:27

чтобы еще лучше понять смысл классов интерфейсов и вообще ООП, рекомендуем прочитать UML диаграммы классов
https://intellect.icu/diagramma-klassov-class-diagram-4825

Отношения классов в UML
https://intellect.icu/otnosheniya-klassov-v-uml-4301


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Object oriented programming

Terms: Object oriented programming