User Tools

Site Tools


java:generic_interface_example

Παράδειγμα χρήσης διεπαφής σε συνδυασμό με Abstract κλάση (περιέχει και generics στο τελευταίο μέρος της λύσης)

Ας επανέλθουμε στο παράδειγμα χρήσης των interfaces ως τύπους δεδομένων και ας αλλάξουμε ελαφρά το interface MyComparable ώστε να περιέχει και την μέθοδο isEqual που ελέγχει την ισότητα.

MyComparable.java
public interface MyComparable {
  public boolean isLarger(MyComparable other) throws InvalidComparableTypeException;
  public boolean isEqual(MyComparable other) throws InvalidComparableTypeException;
}

Η κλάση InvalidComparableTypeException ορίζεται ως εξής:

InvalidComparableTypeException.java
class InvalidComparableTypeException extends Exception {
}

και είναι απαραίτητη ώστε να σηματοδοτήσουμε την πιθανή σύγκριση ασύμβατων μεταξύ τους τύπων δεδομένων.

Στη συνέχεια κατασκευάζουμε την κλάση Rectangle που υλοποιεί το συγκεκριμένο interface:

Rectangle.java
public class Rectangle implements MyComparable{
 
  // the Rectangle class has 3 fields
  private int width;
  private int height;
  private Point origin;
 
  // the Rectangle class has one constructor
  public Rectangle(int initWidth, int initHeight, Point initOrigin) {
    width = initWidth;
    height = initHeight;
    origin = initOrigin;
  }
 
  public Rectangle(int initWidth, int initHeight, int xPos, int yPos) {
    this(initWidth, initHeight, new Point(xPos,yPos));
  }
 
  public Rectangle(int initWidth, int initHeight) {
    this(initWidth, initHeight, 0, 0);
  }
 
  public void setWidth(int newWidth ) {
    width = newWidth;
  }
 
  public int getWidth() { return width; }
 
  public void setHeight(int newHeight ) {
    height = newHeight;
  }
 
  public int getHeight() { return height; }
 
  public void setOrigin(Point newOrigin) {
    origin = newOrigin;
  }
 
  public Point getOrigin() { return origin; }
 
  @Override
  public boolean isLarger(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof Rectangle ) {
      Rectangle otherRect = (Rectangle) other;
      return (this.getArea() > otherRect.getArea());
    }
    throw new InvalidComparableTypeException();
  }
 
  @Override
  public boolean isEqual(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof Rectangle ) {
      Rectangle otherRect = (Rectangle) other;
      return (this.getArea() == otherRect.getArea());
    }
    throw new InvalidComparableTypeException();
  }
 
  @Override
  public String toString() {
    return origin.toString() + ", " + width + " X " + height ;
  }
 
  public double getArea() {
       return (double)width * height;
  }
} 

Παρατηρήστε ότι υλοποιούμε το interface MyComparable συγκρίνοντας τα αντικείμενου τύπου Rectangle με βάση το εμβαδόν τους. Επιπλέον ορίζουμε την κλάση MyComparableUtil που δίνει δύο μεθόδους σύγκρισης αντικείμενων τύπου MyComparable:

MyComparableUtil.java
public class MyComparableUtil {
  /**
   * Returns the larger among the two objects. If objects are equal returns null.
   */
  public static MyComparable findLarger(MyComparable object1, MyComparable object2) throws InvalidComparableTypeException {
    if(object1.isLarger(object2))
      return object1;
    else if( object1.isEqual(object2) )
      return null;
    return object2;
  }
 
  /**
   * Returns the smaller among the two objects. If objects are equal returns null.
   */
  public static MyComparable findSmaller(MyComparable object1, MyComparable object2) throws InvalidComparableTypeException {
    if(!object1.isLarger(object2))
      return object1;
    else if( object1.isEqual(object2) )
      return null;
    return object2;
  }
}

Τέλος η κλάση CompareObjects παρέχει τη main μέθοδο που θα χρειαστούμε για να εκτελέσουμε το πρόγραμμα.

CompareObjects.java
public class CompareObjects {
  public static void main (String []args) {
    try {
      Point p = new Point(1,1);
      Rectangle rec1 = new Rectangle(10, 20, p);
      Rectangle rec2 = new Rectangle(10, 22, p);
 
      System.out.println("Larger objet is "+ MyComparableUtil.findLarger(rec1, rec2));
    } catch(InvalidComparableTypeException ex) {
      System.err.println("Unable to compare objects!");
    }
  }
}

Ας υποθέσουμε τώρα ότι θέλετε να φτιάξετε την κλάση Circle που απεικονίζει τον κύκλο και υλοποιεί και αυτή το interface MyComparable.

Circle.java
public class Circle implements MyComparable{
  private Point origin;
  private int radius;
 
  public Circle(Point origin, int radius) {
    this.origin = origin;
    this.radius = radius;
  }
 
  public void setOrigin(Point newOrigin) {
    origin = newOrigin;
  }
 
  public Point getOrigin() { return origin; }
 
  public void setRadius(int newRadius ) {
    radius = newRadius;
  }
 
  public int getRadius() { return radius; }
 
  public double getArea() {
    return 3.14159 * radius * radius;
  }
 
  @Override
  public boolean isLarger(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof Circle ) {
      Circle otherRect = (Circle) other;
      return (this.getArea() > otherRect.getArea());
    }
    throw new InvalidComparableTypeException();
  }
 
  @Override
  public boolean isEqual(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof Circle ) {
      Circle otherRect = (Circle) other;
      return (this.getArea() == otherRect.getArea());
    }
    throw new InvalidComparableTypeException();
  }
 
  public String toString() {
    return origin.toString()+" radius: "+radius;
  }
}

Ας προσπαθήσουμε τώρα στην CompareObjects να συγκρίνουμε ένα ορθογώνιο παραλληλόγραμμο με ένα κύκλο.

CompareObjects.java
public class CompareObjects {
  public static void main (String []args) {
    try {
      Point p = new Point(1,1);
      Rectangle rect = new Rectangle(10, 20, p);
      Circle circle = new Circle(p, 12);
 
      System.out.println("Larger objet is "+ MyComparableUtil.findLarger(rect, circle));
    } catch(InvalidComparableTypeException ex) {
      System.err.println("Unable to compare objects!");
    }
  }
}

Παρατηρούμε ότι η παραπάνω κλάση μεταγλωττίζεται, αλλά όταν θα επιχειρήσετε να την τρέξετε θα παραχθεί το exception InvalidComparableTypeException και θα τυπωθεί το μήνυμα μέσα στο catch block. Eίναι αδύνατον να συγκρίνουμε διδιάστατα αντικείμενα που ανήκουν σε διαφορετικά σχήματα, αν και το μέτρο σύγκρισης του εμβαδού που υιοθετούμε είναι κοινό και στις δύο κλάσεις.

Προκειμένου να μπορούμε να συγκρίνουμε διαφορετικά διδιάστατα σχήματα μεταξύ τους η λύση είναι να φτιάξουμε μία ενδιάμεση κλάση η οποία να συγκρίνει τα διδιάστατα σχήματα με βάση το εμβαδό τους, δηλαδή με βάση την μέθοδο getArea(). Ας ονομάσουμε την κλάση αυτή TwoDShape:

TwoDShape.java
public abstract class TwoDShape implements MyComparable {
 
  public final boolean isLarger(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof TwoDShape ) {
      TwoDShape obj = (TwoDShape)other;
      if( this.getArea() > obj.getArea() ) 
        return true;
      else 
        return false;
     }
     throw new InvalidComparableTypeException();
  }
 
  public final boolean isEqual(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof TwoDShape ) {
      TwoDShape obj = (TwoDShape)other;
      if( this.getArea() == obj.getArea() ) 
        return true;
      else 
        return false;
    }
    throw new InvalidComparableTypeException();
  }
 
  public abstract double getArea();
}

Παρατηρήστε τα εξής:

  1. Η μέθοδος isLarger για τα διδιάστατα σχήματα βασίζεται στη μέθοδο getArea(). Η μέθοδος getArea() ορίζεται στην κλάση TwoDShape, αλλά η υλοποίηση της εξαρτάται από το εκάστοτε διδιάστατο σχήμα. Κατά συνέπεια, η μέθοδος δηλώνεται ως abstract. Η μέθοδος getArea() θα οριστεί σε κάθε υποκλάση της TwoDShape. Η κλάση που περιέχει μία abstract μέθοδο είναι και αυτή abstract.
  2. H μέθοδος isLarger συγκρίνει τα διδιάστατα σχήματα με βάση το εμβαδό τους. Προκειμένου κάποια υποκλάση να μην επαναορίσει την μέθοδο isLarger με τρόπο που δεν είναι επιθυμητός, ώστε να εξακολουθούμε να μπορούμε να συγκρίνουμε διδιάστατα σχήματα μεταξύ τους, ορίζουμε την μέθοδο ως final.

Με βάση τα παραπάνω οι κλάσεις Rectangle και Circle γίνονται ως εξής:

Rectangle.java
public class Rectangle extends TwoDShape {
 
  // the Rectangle class has 3 fields
  private int width;
  private int height;
  private Point origin;
 
  // the Rectangle class has one constructor
  public Rectangle(int initWidth, int initHeight, Point initOrigin) {
    width = initWidth;
    height = initHeight;
    origin = initOrigin;
  }
 
  public Rectangle(int initWidth, int initHeight, int xPos, int yPos) {
    this(initWidth, initHeight, new Point(xPos,yPos));
  }
 
  public Rectangle(int initWidth, int initHeight) {
    this(initWidth, initHeight, 0, 0);
  }
 
  public void setWidth(int newWidth ) {
    width = newWidth;
  }
 
  public int getWidth() { return width; }
 
  public void setHeight(int newHeight ) {
    height = newHeight;
  }
 
  public int getHeight() { return height; }
 
  public void setOrigin(Point newOrigin) {
    origin = newOrigin;
  }
 
  public Point getOrigin() { return origin; }
 
  @Override
  public String toString() {
    return origin.toString() + ", " + width + " X " + height ;
  }
 
  public double getArea() {
       return width * height;
  }
} 
Circle.java
public class Circle extends TwoDShape {
  private Point origin;
  private int radius;
 
  public Circle(Point origin, int radius) {
    this.origin = origin;
    this.radius = radius;
  }
 
  public void setOrigin(Point newOrigin) {
    origin = newOrigin;
  }
 
  public Point getOrigin() { return origin; }
 
  public void setRadius(int newRadius ) {
    radius = newRadius;
  }
 
  public int getRadius() { return radius; }
 
  public double getArea() {
    return 3.14159 * radius * radius;
  }
 
  public String toString() {
    return origin.toString()+" radius: "+radius;
  }
}

Επιχειρώντας τώρα να τρέξετε τη κλάση CompareObjects παρατηρήστε ότι τρέχει χωρίς πρόβλημα.

Εισάγοντας μία νέα κλάση που υλοποιεί το Interface

Σε αναλογία με τα παραπάνω ας υποθέσουμε ότι φτιάχνουμε την κλάση ThreeDShape που υλοποιεί και αυτή το interface MyComparable. Η κλάση ThreeDShape έχει και αυτή την δική της υποκλάση Cuboid.

ThreeDShape.java
public abstract class ThreeDShape implements MyComparable {
 
  public final boolean isLarger(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof ThreeDShape ) {
      ThreeDShape obj = (ThreeDShape)other;
      if( this.getArea() > obj.getArea() ) 
        return true;
      else 
        return false;
     }
     throw new InvalidComparableTypeException();
  }
 
  public final boolean isEqual(MyComparable other) throws InvalidComparableTypeException {
    if( other instanceof ThreeDShape ) {
      ThreeDShape obj = (ThreeDShape)other;
      if( this.getArea() == obj.getArea() ) 
        return true;
      else 
        return false;
    }
    throw new InvalidComparableTypeException();
  }
 
  public abstract double getVolume();
 
  public abstract double getArea();
}
Cuboid.java
public class Cuboid extends ThreeDShape {
  Rectangle rec;
  int depth;
 
  public Cuboid( Rectangle rec, int depth) {
    this.rec = rec;
    this.depth = depth;
  }
 
  public double getVolume() {
    return rec.getArea() * depth;
  }
 
  public double getArea() {
    return 2 * ( depth * rec.getWidth() + depth * rec.getHeight() + rec.getArea() ); 
  }
 
  public String toString() {
    return rec.toString() + " ,depth: "+depth;
  }
}

Ας υποθέσουμε τώρα ότι επιχειρούμε συγκρίνουμε ένα διδιάστατο με ένα τρισδιάστατο σχήμα. Μεταβάλλουμε κατάληλλα την κλάση CompareObjects και επιχειρούμε να μεταγλωττίσουμε και να τρέξουμε.

CompareObjects.java
public class CompareObjects {
  public static void main (String []args) {
    try {
      Point p = new Point(1,1);
      Rectangle rect = new Rectangle(10, 20, p);
      Cuboid cuboid = new Cuboid(rect, 15);
 
      System.out.println("Larger objet is "+ MyComparableUtil.findLarger(rect, cuboid));
    } catch(InvalidComparableTypeException ex) {
      System.err.println("Unable to compare objects!");
    }
  }
}

Ο παρακάτω κώδικας δεν θα έπρεπε να δουλεύει καθώς επιχειρεί να συγκρίνει ένα διδιάστατο με ένα τρισδιάστατο σχήμα που είναι μη συγκρίσιμα. Πράγματι δεν δουλεύει καθώς παράγει ένα exception InvalidComparableTypeException. Ιδανικά όμως θα θέλαμε ένα σφάλμα αυτού του τύπου να το αντιληφθούμε κατά την μεταγλώττιση του προγράμματος και όχι κατά την εκτέλεση του.

Η λύση στο πρόβλημα είναι η χρήση παραμετρικών τύπων δεδομένων (generics). Οι παραμετρικοί τύποι δεδομένων έχουν την δυνατότητα να αντιλαμβάνονται ασυμβατότητες τύπων δεδομένων κατά την μεταγλώττιση και όχι κατά την εκτέλεση. Δείτε πως διαμορφώνονται όλες οι παραπάνω κλάσεις με χρήση generics.

Χρήση Generics για το προηγούμενο παράδειγμα

MyComparable.java
public interface MyComparable<T> {
  public boolean isLarger(T other);
  public boolean isEqual(T other);
}
TwoDShape.java
public abstract class TwoDShape implements MyComparable<TwoDShape> {
 
  public final boolean isLarger(TwoDShape other) {
      if( this.getArea() > other.getArea() ) 
        return true;
      else 
        return false;
  }
 
  public final boolean isEqual(TwoDShape other) {
      if( this.getArea() == other.getArea() ) 
        return true;
      else 
        return false;
  }
 
  public abstract double getArea();
}
ThreeDShape.java
public abstract class ThreeDShape implements MyComparable<ThreeDShape> {
 
  public final boolean isLarger(ThreeDShape other) {
      if( this.getArea() > other.getArea() ) 
        return true;
      else 
        return false;
  }
 
  public final boolean isEqual(ThreeDShape other) {
      if( this.getArea() == other.getArea() ) 
        return true;
      else 
        return false;
  }
 
  public abstract double getArea();
  public abstract double getVolume(); 
}
MyComparableUtil.java
public class MyComparableUtil <T extends MyComparable<T>> {
 
  public T findLarger(T object1, T object2) {
    if (object1.isLarger(object2))
      return object1;
    else if (object1.isEqual(object2))
      return null;
    else 
      return object2;
  }
 
  public T findSmaller(T object1, T object2) {
    if (!(object1.isLarger(object2)))
      return object1;
    else if (object1.isEqual(object2))
      return null;
    else 
      return object2;
  }
}

Παρατηρήστε τα εξής:

  1. Δεν έχει νόημα η παραγωγή του InvalidComparableTypeException, καθώς η ασυμβατότητα των τύπων εντοπίζεται κατά την διάρκεια της μεταγλώττισης και όχι της εκτέλεσης του κώδικα.
  2. Δεν είναι δυνατόν να ορίσετε την κλάση MyComparableUtil ως στατική κλάση, καθώς περιέχει παραμετρικούς τύπους και ο compiler δεν το επιτρέπει.

Προσαρμόζουμε κατάλληλα την κλάση CompareObjects, ώστε να ανταποκρίνεται στις παραπάνω αλλαγές. Αν προσπαθήσετε να μεταγλωττίσετε την CompareObjects θα δείτε ότι είναι αδύνατον να το κάνετε.

CompareObjects.java
public class CompareObjects {
  public static void main (String []args) {
    Point p = new Point(1,1);
    Rectangle rect = new Rectangle(10, 20, p);
    Cuboid cuboid = new Cuboid(rect, 15);
 
    MyComparableUtil<TwoDShape> util = new MyComparableUtil<>();
 
    System.out.println("Larger objet is "+ util.findLarger(rect, cuboid));
  }
}

Το μήνυμα που εμφανίζει ο compiler είναι το παρακάτω

compiler-error.txt
CompareObjects.java:9: error: method findLarger in class MyComparableUtil<T> cannot be applied to given types;
    System.out.println("Larger objet is "+ util.findLarger(rect, cuboid));
                                               ^
  required: TwoDShape,TwoDShape
  found: Rectangle,Cuboid
  reason: actual argument Cuboid cannot be converted to TwoDShape by method invocation conversion
  where T is a type-variable:
    T extends MyComparable<T> declared in class MyComparableUtil
java/generic_interface_example.txt · Last modified: 2020/02/25 09:34 by 127.0.0.1