java:interfaces
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
java:interfaces [2017/02/03 07:57] – gthanos | java:interfaces [2017/02/03 09:51] (current) – removed gthanos | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Διεπαφές (Interfaces) ====== | ||
- | |||
- | ===== Εισαγωγικά ===== | ||
- | |||
- | Κατά την ανάπτυξη προγραμμάτων είναι σημαντικό να είναι τυποποιημένος ο τρόπος αλληλεπίδρασης ανάμεσα σε διαφορετικά συστήματα. Η ύπαρξη ενός " | ||
- | |||
- | Υπακούοντας στην παραπάνω αρχή, η Java εισάγει την έννοια της διεπαφής (**interface**). Τα // | ||
- | - **τους προγραμματιστές που το χρησιμοποιούν: | ||
- | - **τους προγραμματιστές που το υλοποιούν: | ||
- | |||
- | Από τα παραπάνω εξάγεται ότι το // | ||
- | |||
- | Ως παράδειγμα, | ||
- | * **setTimer: | ||
- | * **startTimer: | ||
- | * **endTimer: | ||
- | |||
- | Ας υποθέσουμε τώρα ότι ένας κατασκευαστής ηλεκτρικών συσκευών (κλιμαστιστικά, | ||
- | |||
- | Οποιοσδήποτε προγραμματιστής χρησιμοποιεί το παραπάνω // | ||
- | |||
- | Αντιπαραθέστε μια κατάσταση όπου οι συσκευές δεν τηρούν αυτό το συμβόλαιο. Σε αυτή την περίπτωση κάθε συσκευή θα έπρεπε να υλοποιεί τον δικό της timer. Η προτυποποίηση που παρέχει ο μηχανισμός του // | ||
- | - την εύκολη και χωρίς σφάλματα επαναχρησιμοποίηση κώδικα σε διαφορετικά προγράμματα ή | ||
- | - την αλλαγή της υφιστάμενης υλοποίησης μιας κλάσης, | ||
- | |||
- | ===== Ορίζοντας ένα Interface ===== | ||
- | |||
- | Παρακάτω δίνεται το παράδειγμα ορισμού ενός 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 ορίζεται η ιδιότητα της κληρονομικότητας. __Η διαφορά είναι ότι ενώ στην κλάση μπορούμε να έχουμε μόνο μία γονική κλάση, στα interfaces μπορούμε να έχουμε περισσότερα του ενός γονικά 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. | ||
- | |||
- | Σε συνέχεια του προηγούμενου παραδείγματος θα επιχειρήσουμε να δημιουργήσουμε δύο διαφορετικές κλάσεις που υλοποιούν το συγκεκριμένο 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("" | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | <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 με την προσθήκη ή αλλαγή των υφιστάμενων μεθόδων θα πάψουν να το υλοποιούν. Επομένως μία μονομερής αλλαγή του 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 δεν υλοποιεί πλέον το αρχικό interface. Αυτό μπορεί να συνεχίσει να υλοποιείται εάν η νέα μέθοδος έχει μία default υλοποίηση μέσα στο 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 | ||
- | */ | ||
- | default String invertHalf(String str) { | ||
- | int len = str.length(); | ||
- | | ||
- | | ||
- | | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | |||
- | |||
- | ===== Στατικές μέθοδοι ===== | ||
- | |||
- | Εκτός από //default// μεθόδους ένα 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 | ||
- | */ | ||
- | 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> | ||
- | **__Παρατήρηση: | ||
- | </ | ||
- | |||
- | |||
- | | Προηγούμενο : [[ :java:ant | Αυτόματη μεταγλώττιση με χρήση Apache Ant ]] | [[ :toc | Περιεχόμενα ]] | Επόμενο: | ||
- | |||
- | |||
java/interfaces.1486108672.txt.gz · Last modified: 2017/02/03 07:57 by gthanos