This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
java:interfaces [2017/01/31 14:43] gthanos |
java:interfaces [2017/02/03 09:51] gthanos removed |
||
|---|---|---|---|
| Line 3: | Line 3: | ||
| ===== Εισαγωγικά ===== | ===== Εισαγωγικά ===== | ||
| - | Κατά την ανάπτυξη προγραμμάτων είναι σημαντικό να είναι τυποποιημένος ο τρόπος αλληλεπίδρασης ανάμεσα σε διαφορετικά συστήματα. Η ύπαρξη ενός " | + | Κατά την ανάπτυξη προγραμμάτων είναι σημαντικό να είναι τυποποιημένος ο τρόπος αλληλεπίδρασης ανάμεσα σε διαφορετικά συστήματα. Η ύπαρξη ενός " |
| Υπακούοντας στην παραπάνω αρχή, η Java εισάγει την έννοια της διεπαφής (**interface**). Τα // | Υπακούοντας στην παραπάνω αρχή, η Java εισάγει την έννοια της διεπαφής (**interface**). Τα // | ||
| - | - τους προγραμματιστές που το χρησιμοποιούν και | + | - **τους προγραμματιστές που το χρησιμοποιούν:** οι προγραμματιστές γράφουν την εφαρμογή τους ώστε να χρησιμοποιεί τις μεθόδους του // |
| - | - τους προγραμματιστές που το υλοποιούν. | + | - **τους προγραμματιστές που το υλοποιούν:** εφόσον θέλουν να ενσωματώσουν τη λειτουργικότητα που υποδεικνύει το // |
| - | Οι πρώτοι | + | Από τα παραπάνω εξάγεται ότι |
| - | Οι δεύτεροι, | + | Ως παράδειγμα, |
| - | + | ||
| - | Από τα παραπάνω εξάγεται ότι το // | + | |
| - | + | ||
| - | Ως παράδειγμα, | + | |
| * **setTimer: | * **setTimer: | ||
| * **startTimer: | * **startTimer: | ||
| * **endTimer: | * **endTimer: | ||
| - | Ας υποθέσουμε τώρα ότι ένας κατασκευαστής ηλεκτρικών συσκευών (κλιμαστιστικά, | + | Ας υποθέσουμε τώρα ότι ένας κατασκευαστής ηλεκτρικών συσκευών (κλιμαστιστικά, |
| - | + | ||
| - | Οποιοσδήποτε προγραμματιστής χρησιμοποιεί το παραπάνω | + | |
| - | + | ||
| - | Αντιπαραθέστε μια κατάσταση όπου οι συσκευές | + | |
| - | - την εύκολη και χωρίς σφάλματα | + | |
| - | - την αλλαγή της | + | |
| - | + | ||
| - | ===== Ορίζοντας ένα Interface ===== | + | |
| - | + | ||
| - | Παρακάτω δίνεται το παράδειγμα | + | |
| - | + | ||
| - | <code java> | + | |
| - | public interface MyInterface extends Interface1, Interface2, Interface3 { | + | |
| - | + | ||
| - | // constant declarations | + | |
| - | // base of natural logarithms | + | |
| - | double E = 2.718282; | + | |
| - | double PI = 3.14159; | + | |
| - | + | ||
| - | // method signatures | + | |
| - | public void interfaceMethod(int i, double x); | + | |
| - | public int interfaceMethod2(String s); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | * **public ή κανένας προσδιοριστής πρόσβασης: | + | |
| - | * **interface: | + | |
| - | * **το όνομα του interface: | + | |
| - | * **extends: | + | |
| - | + | ||
| - | Τόσο στις κλάσεις όσο και στα interfaces ορίζεται | + | |
| - | + | ||
| - | <WRAP tip 80% center round> | + | |
| - | Ακριβώς όπως και οι κλάσεις κάθε interface πρέπει να βρίσκεται σε ξεχωριστό αρχείο, | + | |
| - | </ | + | |
| - | + | ||
| - | ==== Το σώμα του Interface ==== | + | |
| - | + | ||
| - | Ένα // | + | |
| - | + | ||
| - | <code java> | + | |
| - | double E = 2.718282; | + | |
| - | double PI = 3.14159; | + | |
| - | </ | + | |
| - | + | ||
| - | Τα πεδία αυτά εξ ορισμού (by default) '' | + | |
| - | + | ||
| - | Οι μέθοδοι σε ένα interface κατά κανόνα είναι **abstract**, | + | |
| - | + | ||
| - | <code java> | + | |
| - | public interface MyInterface extends Interface1, Interface2, Interface3 { | + | |
| - | + | ||
| - | // constant declarations | + | |
| - | // base of natural logarithms | + | |
| - | double E = 2.718282; | + | |
| - | double PI = 3.14159; | + | |
| - | + | ||
| - | // method signatures | + | |
| - | public void interfaceMethod1(int i, double x); | + | |
| - | public int interfaceMethod2(String s); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | ===== Παράδειγμα interface - H Στοίβα (Stack) ===== | + | |
| - | + | ||
| - | Ας υποθέσουμε ότι θέλουμε να δηλώσουμε μέσω ενός interface της μεθόδους που πρέπει να υποστηρίζει μία στοίβα. Η στοίβα είναι μία δομή αποθήκευσης δεδομένων τύπου **Last In First Out (LIFO)**, όπου το αντικείμενο αποθηκεύεται στη στοίβα τελευταίο εξάγεται από αυτή πρώτο. Κάθε στοίβα θα πρέπει | + | |
| - | * μέθοδο επιστροφής του μεγέθους της στοίβας. | + | |
| - | * μέθοδο ένθεσης στην κορυφή της στοίβας. | + | |
| - | * μέθοδο απόσβεσης του κορυφαίου στοιχείου. | + | |
| - | * μέθοδο επιστροφής του κορυφαίου στοιχείου της στοίβας χωρίς διαγραφή από τη στοίβα. | + | |
| - | + | ||
| - | Παρακάτω δηλώνεται το //interface Stack// | + | |
| - | <code java Stack.java> | + | |
| - | public interface Stack { | + | |
| - | public int size(); | + | |
| - | public void push(Object o); // ένθεση στην κορυφή της στοίβας. | + | |
| - | public Object pop(); | + | |
| - | public Object top(); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | ===== Υλοποιώντας το Interface ===== | + | |
| - | + | ||
| - | Μία κλάση υλοποιεί ένα interface μόνο εάν υλοποιεί ΟΛΕΣ τις μεθόδους του interface. Έάν έστω και μία μέθοδος δεν υλοποιείται τότε η κλάση | + | |
| - | + | ||
| - | Σε συνέχεια του προηγούμενου παραδείγματος θα επιχειρήσουμε να δημιουργήσουμε δύο διαφορετικές κλάσεις που υλοποιούν το συγκεκριμένο interface. Οι κλάσεις αυτές είναι η ArrayStack που υλοποιεί τη στοίβα μέσω ενός πίνακα και η LinkedStack που υλοποιεί τη στοίβα διασυνδέοντας επιμέρους αντικείμενα μεταξύ τους σε αναλογία με μία διασυνδεδεμένη λίστα. | + | |
| - | + | ||
| - | <code java ArrayStack.java> | + | |
| - | public class ArrayStack implements Stack { | + | |
| - | public int capacity; | + | |
| - | public Object []array; | + | |
| - | public int size; | + | |
| - | + | ||
| - | public ArrayStack() { | + | |
| - | this(256); | + | |
| - | } | + | |
| - | + | ||
| - | public ArrayStack(int capacity) { | + | |
| - | array = new Object[capacity]; | + | |
| - | this.capacity = capacity; | + | |
| - | size = 0; | + | |
| - | } | + | |
| - | + | ||
| - | public int size() { | + | |
| - | return size; | + | |
| - | } | + | |
| - | + | ||
| - | public void push(Object o) { | + | |
| - | + | ||
| - | if( size >= capacity-1 ) { | + | |
| - | capacity *= 2; | + | |
| - | Object []newArray = new Object[capacity]; | + | |
| - | for(int i=0; i< | + | |
| - | newArray[i] = array[i]; | + | |
| - | array = newArray; | + | |
| - | newArray = null; | + | |
| - | } | + | |
| - | array[size++] = o; | + | |
| - | } | + | |
| - | + | ||
| - | public Object pop() { | + | |
| - | return array[--size]; | + | |
| - | } | + | |
| - | + | ||
| - | public Object top() { | + | |
| - | return array[size-1]; | + | |
| - | } | + | |
| - | + | ||
| - | public String toString() { | + | |
| - | String str = " | + | |
| - | for(int i=size-1; i>=0; i--) | + | |
| - | str += array[i].toString()+" | + | |
| - | return str + " | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | <code java LinkedStack.java> | + | |
| - | + | ||
| - | class LinkedNode { | + | |
| - | private Object o; | + | |
| - | private LinkedNode next; | + | |
| - | + | ||
| - | public LinkedNode(LinkedNode nxt, Object e) { | + | |
| - | next = nxt; | + | |
| - | o = e; | + | |
| - | } | + | |
| - | + | ||
| - | public LinkedNode(Object e) { | + | |
| - | this(null, e); | + | |
| - | } | + | |
| - | + | ||
| - | public Object getElement() { return o; } | + | |
| - | public LinkedNode getNext() { return next; } | + | |
| - | + | ||
| - | public void setElement(Object e) { o = e; } | + | |
| - | public void setNext(LinkedNode node) { next = node; } | + | |
| - | } | + | |
| - | + | ||
| - | public class LinkedStack implements Stack { | + | |
| - | int size; | + | |
| - | LinkedNode head; | + | |
| - | + | ||
| - | public LinkedStack() { | + | |
| - | size=0; | + | |
| - | head = null; | + | |
| - | } | + | |
| - | + | ||
| - | public int size() { | + | |
| - | return size; | + | |
| - | } | + | |
| - | + | ||
| - | public void push(Object o) { | + | |
| - | // the following is OK even if head == null. | + | |
| - | head = new LinkedNode(head, | + | |
| - | size++; | + | |
| - | } | + | |
| - | + | ||
| - | public Object pop() { | + | |
| - | LinkedNode pN = head; | + | |
| - | head = head.getNext(); | + | |
| - | size--; | + | |
| - | return pN.getElement(); | + | |
| - | } | + | |
| - | + | ||
| - | public Object top() { | + | |
| - | return head; | + | |
| - | } | + | |
| - | + | ||
| - | public String toString() { | + | |
| - | String str = " | + | |
| - | LinkedNode curr = head; | + | |
| - | while(curr != null) | + | |
| - | str += curr.getElement().toString(); | + | |
| - | return str + " | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Παρατηρούμε δηλαδή ότι έχουμε υλοποιήσει το συγκεκριμένο interface μέσα από δύο διαφορετικές κλάσεις. Αν και η υλοποίηση των κλάσεων διαφέρει σημαντικά, | + | |
| - | + | ||
| - | + | ||
| - | ===== Χρησιμοποιώντας ένα interface | + | |
| - | + | ||
| - | Ας υποθέσουμε τώρα ότι θέλουμε να χρησιμοποιήσουμε μία στοίβα για να αντιμεταθέσουμε τα στοιχεία ενός πίνακα χαρακτήρων. Ο πίνακας αρχικά περιέχει την αγγλική αλφαβήτα (26 χαρακτήρες) και θέλουμε να αντιστρέψουμε την σειρά με την οποία αποθηκεύονται οι χαρακτήρες στον πίνακα. Η μέθοδος invertArray παρακάτω αντιμεταθέτει τα στοιχεία του πίνακα array με χρήση της στοίβας stk. | + | |
| - | + | ||
| - | <code java ArrayManipulator.java> | + | |
| - | public class ArrayManipulator { | + | |
| - | public static void main(String []args) { | + | |
| - | Character alphabet[] = {' | + | |
| - | printCharacterArray(alphabet); | + | |
| - | System.out.println(" | + | |
| - | + | ||
| - | Stack stack = new ArrayStack(); | + | |
| - | invertArray(alphabet, | + | |
| - | + | ||
| - | printCharacterArray(alphabet); | + | |
| - | } | + | |
| - | + | ||
| - | public static void invertArray(Object []array, Stack stk) { | + | |
| - | for(int i=0; i< | + | |
| - | stk.push(array[i]); | + | |
| - | for(int i=0; i< | + | |
| - | array[i] = stk.pop(); | + | |
| - | } | + | |
| - | + | ||
| - | public static void printCharacterArray(Character []array) { | + | |
| - | for(Character c : array) | + | |
| - | System.out.print(c+" | + | |
| - | System.out.println("" | + | |
| - | } | + | |
| - | } | + | |
| - | </code> | + | |
| - | + | ||
| - | <WRAP important 80% center round> | + | |
| - | Παρατηρήστε ότι η μέθοδος | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP todo 80% center round> | + | |
| - | Στο παραπάνω πρόγραμμα, | + | |
| - | <code java> | + | |
| - | Stack stack = new ArrayStack(); | + | |
| - | </ | + | |
| - | σε | + | |
| - | <code java> | + | |
| - | Stack stack = new LinkedStack(); | + | |
| - | </ | + | |
| - | + | ||
| - | Παρατηρήστε αν σας βγάζει κάποιο μήνυμα ο // | + | |
| - | </ | + | |
| - | + | ||
| - | ===== Μεταβάλλοντας ένα υφιστάμενο Interface ===== | + | |
| - | + | ||
| - | Κάποιες φορές στον προγραμματισμό εμφανίζεται η ανάγκη να μεταβάλλουμε ένα υφιστάμενο interface ή να το επεκτείνουμε. Το πρόβλημα σε αυτές τις περιπτώσεις είναι ότι οι κλάσεις που υλοποιούν το συγκεκριμένο interface με την προσθήκη ή αλλαγή των | + | |
| - | + | ||
| - | + | ||
| - | **1η εναλλακτική: | + | |
| - | <code java> | + | |
| - | public interface DoIt { | + | |
| - | + | ||
| - | void doSomething(int i, double x); | + | |
| - | int doSomethingElse(String s); | + | |
| - | } | + | |
| - | + | ||
| - | public interface DoItPlus extends DoIt { | + | |
| - | + | ||
| - | | + | |
| - | + | ||
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | **2η εναλλακτική: | + | |
| - | <code java> | + | |
| - | public interface DoIt { | + | |
| - | + | ||
| - | void doSomething(int i, double x); | + | |
| - | int doSomethingElse(String s); | + | |
| - | | + | |
| - | // Method body | + | |
| - | | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | ===== Default μέθοδοι ===== | + | |
| - | + | ||
| - | <WRAP 80% important center round> | + | |
| - | **__Παρατήρηση: | + | |
| - | </ | + | |
| - | + | ||
| - | Ας θεωρήσουμε το παρακάτω υποθετικό interface | + | |
| - | + | ||
| - | <code java StringInverter.java> | + | |
| - | public interface StringInverter { | + | |
| - | /** inverts a string | + | |
| - | */ | + | |
| - | String invert(String str); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Ας υποθέσουμε τώρα ότι η κλάση MyString υλοποιεί το παραπάνω interface. | + | |
| - | + | ||
| - | <code java MyString.java> | + | |
| - | import java.lang.*; | + | |
| - | public class MyString implements StringInverter { | + | |
| - | String invert(String str) { | + | |
| - | StringBuffer strb = new StringBuffer(str); | + | |
| - | return strb.reverse().toString(); | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Αν υποθέσουμε ότι το αρχικό interface αλλάζει στο παρακάτω | + | |
| - | + | ||
| - | <code java StringInverter.java> | + | |
| - | public interface StringInverter { | + | |
| - | /** inverts a string | + | |
| - | */ | + | |
| - | String invert(String str); | + | |
| - | /** splits a string in half and inverts | + | |
| - | * the two parts. | + | |
| - | */ | + | |
| - | String invertHalf(String str); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Τώρα η κλάση MyString δεν υλοποιεί πλέον το αρχικό | + | |
| - | + | ||
| - | <code java StringInverter.java> | + | |
| - | public interface StringInverter { | + | |
| - | | + | |
| - | */ | + | |
| - | String invert(String str); | + | |
| - | | + | |
| - | * the two parts | + | |
| - | */ | + | |
| - | default String invertHalf(String str) { | + | |
| - | int len = str.length(); | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | ===== Στατικές μέθοδοι | + | Γνωρίζοντας ότι όλες οι εναλλακτικές |
| - | Εκτός από //default// μεθόδους ένα interface μπορεί να περιέχει και στατικές (//static//) μεθόδους. Οι στατικές μέθοδοι ορίζονται σε αναλογία με τις στατικές μεθόδους των | + | Υιοθετώντας την έννοια του |
| - | <code java StringInverter.java> | + | Οποιοσδήποτε προγραμματιστής χρησιμοποιεί το παραπάνω |
| - | public interface StringInverter { | + | |
| - | | + | |
| - | */ | + | |
| - | String invert(String str); | + | |
| - | | + | |
| - | * the two parts | + | |
| - | */ | + | |
| - | default String invertHalf(String str) { | + | |
| - | int len = str.length(); | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | + | ||
| - | /** removes character at index from String str | + | |
| - | * and returns the new String. | + | |
| - | */ | + | |
| - | static String removeChar(String str, int index) { | + | |
| - | if( str.length()-1 < index ) { | + | |
| - | return str; | + | |
| - | } | + | |
| - | String str1 = str.substring(0, | + | |
| - | String str2; | + | |
| - | if( str.length() >= index-1 ) { | + | |
| - | str2 = new String("" | + | |
| - | } | + | |
| - | else { | + | |
| - | str2 = str.substring(index+1); | + | |
| - | } | + | |
| - | return str1+str2; | + | |
| - | + | ||
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | <WRAP 80% important center round> | + | Αντιπαραθέστε τώρα την εναλλακτική μη ύπαρξης προσυμφωνημένων |
| - | **__Παρατήρηση:__** Οι **static** | + | |
| - | </ | + | |
| + | Η προτυποποίηση που παρέχει ο μηχανισμός του // | ||
| + | - την εύκολη και χωρίς σφάλματα επαναχρησιμοποίηση κώδικα σε διαφορετικά προγράμματα και | ||
| + | - την αλλαγή της υφιστάμενης υλοποίησης μιας κλάσης που υλοποιεί ένα // | ||
| - | | Προηγούμενο : [[ :java:ant | Αυτόματη μεταγλώττιση με χρήση Apache Ant ]] | [[ :toc | Περιεχόμενα ]] | Επόμενο: | + | | Προηγούμενο : [[ :java:abstract_classes |