java:inner_classes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
java:inner_classes [2016/02/11 13:25]
gthanos [Πρόσβαση στα πεδία της εξωτερικής κλάσης (από την εσωτερική κλάση)]
java:inner_classes [2017/05/12 14:45] (current)
gthanos
Line 1: Line 1:
 ====== Μη στατικές εμφωλευμένες κλάσεις ====== ====== Μη στατικές εμφωλευμένες κλάσεις ======
  
-Οι μη στατικές εμφωλευμένες κλάσεις ή εσωτερικές κλάσεις (inner classes) αποτελούν την γενικότερη περίπτωση εμφώλευσης μίας κλάσης μέσα σε μία άλλη κλάση. ​Δείτε το παρακάτω παράδειγμα απεικόνισης των ζυγών στοιχείων ​ενός ​πίνακα ​15 στοιχείων με χρήση μίας εσωτερικής κλάσης η οποία υλοποιεί ​ένα [[https://​docs.oracle.com/​javase/​7/​docs/​api/​java/​util/​Iterator.html|Iterator]]:+Οι μη στατικές εμφωλευμένες κλάσεις ή εσωτερικές κλάσεις (inner classes) αποτελούν την γενικότερη περίπτωση εμφώλευσης μίας κλάσης μέσα σε μία άλλη κλάση. ​Ας υποθέσουμε ότι θέλουμε να κατασκευάσουμε μία διπλά συνδεδεμένη λίστα η οποία μπορεί να αποθηκεύσει οποιονδήποτε τύπο όμοιων ​αντικειμένων. 
 +Η λίστα θα χρειαστεί ​να έχει δύο βοηθητικές κλάσεις  
 +  - την κλάση που περιγράφει τον κόμβο της λίστας και  
 +  - μία κλάση τύπου //​iterator//​ για την διάτρεξη της λίστας 
 +Οι δύο παραπάνω κλάσεις εξυπηρετεί να δηλωθούν ως εσωτερικές κλάσεις αποκρύπτοντας την ​υλοποίηση των κλάσεων αυτών. Το παραπάνω επιτρέπει η λίστα ​να είναι συμβατή με τα interfaces ​[[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​util/​List.html|java.util.List]] και [[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​util/​Iterator.html|java.util.Iterator]]. Δείτε τον παρακάτω κώδικα που την υλοποιεί.
  
-<code java DataStructure.java> +<code java LinkedList.java> 
-public class DataStructure ​{+public class LinkedList<​E> ​{ 
 +  private Node<​E>​ head, tail;
   ​   ​
-  ​// Create an array +  ​public LinkedList() { 
-  ​private final static int SIZE 15+    ​head ​new Node<>​(null,​ null, null)
-  ​private int[] arrayOfInts ​= new int[SIZE];+    ​tail ​= new Node<>​(null,​ head, null); 
 +    head.setNext(tail);​ 
 +  } 
 +   
 +  private class Node<​E>​ { 
 +    private Node<​E>​ next, prev; 
 +    private E e;
   ​   ​
-  ​public ​DataStructure() { +    ​public ​Node(Node<​E>​ nxt, Node<​E>​ prv, E elem) { 
-    // fill the array with ascending integer values +      next = nxt; 
-    for (int i 0i < SIZE; i++) { +      ​prev ​prv
-      ​arrayOfInts[i] ​i;+      ​elem;
     }     }
 +    ​
 +    public Node<​E>​ getNext() {return next;}
 +    public void setNext(Node<​E>​ nxt) { next = nxt; }
 +    public Node<​E>​ getPrev() {return prev;}
 +    public void setPrev(Node<​E>​ prv) { prev = prv; }
 +    public E getElement() { return e; }
 +    public void setElement(E elem) { e = elem; }
   }   }
   ​   ​
-  ​public void printEven() ​+  ​private class Iterator<​E>​ implements java.util.Iterator ​
-    ​ +    ​Node<​E>​ curr
-    // Print out values of even indices of the array +    ​public Iterator(Node<​E>​ c) { 
-    DataStructureIterator iterator = this.new EvenIterator()+      ​curr = c;
-    ​while (iterator.hasNext()) { +
-      ​System.out.print(iterator.next() + " ");+
     }     }
-    System.out.println();​ 
-  } 
-  ​ 
-  interface DataStructureIterator extends java.util.Iterator<​Integer>​ { }  
- 
-  // Inner class implements the DataStructureIterator interface, 
-  // which extends the Iterator<​Integer>​ interface 
-  ​ 
-  private class EvenIterator implements DataStructureIterator { 
-    ​ 
-    // Start stepping through the array from the beginning 
-    private int nextIndex = 0; 
-    ​ 
     public boolean hasNext() {     public boolean hasNext() {
-      ​ +      if(curr.getNext() !tail) 
-      // Check if the current element is the last in the array +        return true; 
-      return ​(nextIndex <SIZE - 1); +      return false
-    }     +    } 
-    ​ +    public ​next() { 
-    public ​Integer ​next() { +      ​curr curr.getNext(); 
-      ​ +      ​return curr.getElement();​ 
-      // Record a value of an even index of the array +    } 
-      Integer retValue ​Integer.valueOf(arrayOfInts[nextIndex]); +    public void remove() { 
-       +      ​curr.getPrev().setNext(curr.getNext());​ 
-      ​// Get the next even element +      ​curr.getNext().setPrev(curr.getPrev())
-      ​nextIndex += 2+      ​curr = null;
-      ​return retValue;+
     }     }
-    ​ 
-    public void remove() { } 
   }   }
   ​   ​
-  ​public static void main(String s[]) { +  ​Iterator<​E>​ iterator() { 
-    ​ +    ​return ​new Iterator<​E>​(head);
-    // Fill the array with integer values and print out only +
-    // values of even indices +
-    DataStructure ds = new DataStructure();​ +
-    ds.printEven();+
   }   }
-} 
-</​code>​ 
- 
-<WRAP todo center 80% round> Σε αναλογία,​ δημιουργήστε την εσωτερική κλάση **OddIterator** και τη μέθοδο **printOdd()** για την εκτύπωση των μονών αριθμών του πίνακα. ​ 
-</​WRAP>​ 
- 
-Οι εσωτερικές κλάσεις μπορούν να έχουν προσδιοριστές πρόσβασης (//public, protected, package-private,​ private//) όπως και τα πεδία της κλάσης. Θα εξηγήσουμε αναλυτικά στη συνέχεια τη σημασία των προσδιοριστών πρόσβασης για τις εσωτερικές κλάσεις. 
- 
-===== Πρόσβαση στα πεδία της εξωτερικής κλάσης (από την εσωτερική κλάση) ===== 
- 
-Κάθε εσωτερική κλάση έχει πρόσβαση στα πεδία και τις μεθόδους της εξωτερικής κλάσης,​ **ανεξάρτητα από τους προσδιοριστές πρόσβασης που έχουν αυτά**. Διαφορετικά,​ κάθε μέθοδος της εσωτερικής κλάσης μπορεί να έχει πρόσβαση σε μεταβλητές ή μεθόδους της εξωτερικής της κλάσης,​ ακόμη και εάν αυτές είναι δηλωμένες ως **private**. ​ 
- 
-<WRAP tip 80% round> 
-Εάν μία μέθοδος της εσωτερικής κλάσης καλεί μία άλλη μέθοδο η οποία δεν ανήκει στην εσωτερική κλάση, τότε ο compiler αναζητά την μέθοδο στην εξωτερική κλάση. Αν δεν την βρει και στην εξωτερική κλάση τότε εμφανίζει μήνυμα λάθους. ​ 
- 
-Αντίστοιχα,​ εάν μία μέθοδος της εσωτερικής κλάσης χρησιμοποιήσει ένα πεδίο που δεν υπάρχει στην εσωτερική κλάση, ο compiler θα το αναζητήσει στην εξωτερική κλάση και μόνο αν δεν το βρει ούτε εκεί θα εκτυπώσει ένα μήνυμα λάθους. 
-</​WRAP>​ 
- 
-Δείτε το παρακάτω παράδειγμα προσβασιμότητας των πεδίων της εξωτερικής κλάσης από την εσωτερική κλάση: 
- 
-<code java OuterClassAccess.java>​ 
-public class OuterClassAccess { 
-  private int outerPriv; 
-  int outerPckgPriv;​ 
-  protected int outerProt; 
-  public int outerPub; 
-  InnerClass ic; 
-  ​ 
-  private void setOuterPriv(int v) { outerPriv = v; } 
-  private int getOuterPriv() { return outerPriv; } 
-  ​ 
-  void setOuterPckgPriv(int v) { outerPckgPriv = v; } 
-  int getPckgOuterPriv() { return outerPckgPriv;​ } 
-  ​ 
-  protected void setOuterProt(int v) { outerProt = v; } 
-  protected int getOuterProt() { return outerProt; } 
-  ​ 
-  public void setOuterPub(int v) { outerPub = v; } 
-  public int getOuterPub() { return outerPub; }  ​ 
   ​   ​
-  public ​OuterClassAccess() { +  ​// append in the end 
-     ic = new InnerClass(1,2,3,4); +  ​public ​void add(E elem) { 
-     ic.setOuterClassFields(5,6,7,8);+    ​Node<​E>​ plus = new Node<​E>​(tailtail.getPrev()elem); 
 +    tail.getPrev().setNext(plus);​ 
 +    tail.setPrev(plus);
   }   }
   ​   ​
-  public ​String toString() { +  public ​boolean contains(E elem) { 
-    ​return "​outerPriv:​ "​+outerPriv+",​ outerPckgPriv:​ "​+outerPckgPriv+ +    ​Iterator<​E>​ it = iterator()
-           ",​ outerProt: "​+outerProt+",​ outerPub: "​+outerPub; +    ​while(it.hasNext()) { 
-  } +      ​E e it.next()
-   +      ​if( e.equals(elem) ) 
-  private class InnerClass { +        ​return true;
-  ​ +
-    ​public InnerClass(int p, int pp, int pr, int pu) { +
-      ​outerPriv ​p+
-      ​outerPckgPriv = pp; +
-      ​outerProt = pr; +
-      outerPub = pu;+
     }     }
-    ​ +    ​return false;
-    public void setOuterClassFields(int p, int pp, int pr, int pu) { +
-      setOuterPriv(p); +
-      setOuterPckgPriv(pp);​ +
-      setOuterProt(pr);​ +
-      setOuterPub(pu);​ +
-    }    ​+
   }   }
   ​   ​
-  public ​static void main(String args[]) { +  public ​int indexOf(E elem) { 
-    ​OuterClassAccess oc new OuterClassAccess(); +    ​Iterator<​E>​ it iterator(); 
-    ​System.out.println(oc);​ +    int index -1
-  } +    ​while(it.hasNext()) { 
-+      ​E e = it.next(); 
-</​code>​ +      ​index++; 
- +      ​if( e.equals(elem
-<WRAP tip 80% center round> +        return index;
-Παρατηρήστε ότι η εσωτερική κλάση έχει πρόσβαση σε όλα τα πεδία και τις μεθόδους της εξωτερικής κλάσης,​ ανεξάρτητα από τον προσδιοριστή πρόσβασης που έχουν αυτά. +
-</​WRAP>​ +
-==== Κρύβοντας τα πεδία της εξωτερικής κλάσης ==== +
- +
-Στο προηγούμενο παράδειγμα είδαμε την προσβασιμότητα στα πεδία και τις μεθόδους της εξωτερικής κλάσης. Τι γίνεται όμως όταν υπάρχει μία μέθοδος που καλεί μία άλλη μέθοδο η οποία υπάρχει τόσο στη εσωτερική όσο και στην εξωτερική κλάση. Σε αναλογία τι γίνεται όταν μία μέθοδος καλεί ένα πεδίο το οποίο υπάρχει στην εσωτερική και την εξωτερική κλάση; Το παρακάτω παράδειγμα απαντά στις παραπάνω απορίες. +
- +
-<code java ShadowTest.java>​ +
-public class ShadowTest { +
- +
-  public ​int 100; +
- +
-  class FirstLevel { +
- +
-    ​public int x = 200; +
- +
-    void methodInFirstLevel(int x) { +
-      ​System.out.println("x = " + x); +
-      ​System.out.println("​this.x = " ​this.x)+
-      ​System.out.println("​ShadowTest.this.x = " + ShadowTest.this.x);+
     }     }
-    ​ +    return ​-1;
-    String printX() { return ​"X in FirstLevel is "​+x+"​ and "​+ShadowTest.this.printX()+
-  } +
-   +
-  String printX() { return "X in ShadowTest is "+x; } +
- +
-  public static void main(String args[]) { +
-    ShadowTest st = new ShadowTest();​ +
-    ShadowTest.FirstLevel fl = st.new FirstLevel();​ +
-    fl.methodInFirstLevel(23);​ +
-     +
-    System.out.println(st.printX());​ +
-    System.out.println(fl.printX());​ +
-  } +
-+
-</​code>​ +
- +
- +
-===== Δημιουργία αντικειμένων της εσωτερικής κλάσης από άλλες κλάσεις ή στατικές μεθόδους ===== +
- +
-Κατ'​ αρχήν, το πρώτο το οποίο θα πρέπει να σημειώσουμε είναι ότι ένα αντικείμενο της εσωτερικής κλάσης μπορεί να δημιουργηθεί **ΜΟΝΟ ΕΦΟΣΟΝ** υπάρχει ένα αντικείμενο της εξωτερικής κλάσης. Επομένως,​ για να δημιουργήσετε ένα αντικείμενο της εσωτερικής κλάσης θα πρέπει να έχετε ήδη ένα αντικείμενο της εξωτερικής κλάσης. Δείτε το παρακάτω παράδειγμα. +
- +
-<code java OuterClass.java>​ +
-public class OuterClass { +
-  int outer; ​  +
- +
-  public OuterClass(int o) { +
-    outer = o;     +
-  } +
- +
-  class InnerOuterClass { +
-    int inner; +
-     +
-    public InnerOuterClass(int o) { +
-      inner = o; +
-    } +
-  } +
-   +
-+
-</​code>​ +
- +
-<code java TestOuterClass.java>​ +
-public class TestOuterClass { +
-  public static void mian(String args[]) { +
-    OuterClass outer = new OuterClass(5); ​                           //line 3 +
-    OuterClass.InnerOuterClass inner = outer.new InnerOuterClass(4);​ //line 4 +
-  } +
-+
-</​code>​ +
- +
-Αρχικά,​ στην γραμμή 3 δείτε πως δημιουργούμε ένα αντικείμενο της κλάσης **OuterClass**. Στη συνέχεια με δεδομένο ότι έχουμε δημιουργήσει το αντικείμενο τύπου **OuterClass** στο οποίο δείχνει η μεταβλητή outer δημιουργούμε ένα νέο αντικείμενο της εσωτερικής κλάσης,​ όπως παρακάτω. Παρατηρήστε την ιδιαίτερη σύνταξη δημιουργίας αντικειμένων της εσωτερικής κλάσης.  +
-<code java> +
-OuterClass.InnerOuterClass inner = outer.new InnerOuterClass(4);​ +
-</​code>​ +
- +
-<WRAP todo 80% round center>​ +
-Είναι προφανές ότι μπορούμε να φτιάξουμε πολλαπλά αντικείμενα της εσωτερικής κλάσης,​ με την προϋπόθεση ότι έχουμε τουλάχιστον ένα αντικείμενο της εξωτερικής κλάσης. Τι γίνεται όμως αν παραλείψετε να αρχικοποιήσετε την μεταβλητή της γονικής κλάσης,​ όπως παρακάτω:​ +
-<code java TestOuterClass.java>​ +
-public class TestOuterClass { +
-  public static void mian(String args[]) { +
-    OuterClass outer; ​                                               //line 3 +
-    OuterClass.InnerOuterClass inner = outer.new InnerOuterClass(4);​ //line 4 +
-  } +
-+
-</​code>​ +
-Δοκιμάστε το. +
-</​WRAP>​ +
- +
-==== Χρήση προσδιοριστών πρόσβασης στην εσωτερική κλάση ==== +
- +
-Στο προηγούμενο παράδειγμα δοκιμάστε να αλλάξετε τον προσδιοριστή πρόσβασης της κλάσης **InnerOuterClass** από package private σε private. Σε αυτή την περίπτωση θα παρατηρήσετε ότι η κλάση **TestOuterClass** δεν μεταγλωττίζεται. Συγκεκριμένα,​ o compiler εμφανίζει το παρακάτω μήνυμα λάθους,​ σημειώνοντας ότι ο προσδιοριστής πρόσβασης **private** δεν επιτρέπει τη πρόσβαση από μία άλλη κλάση στο συγκεκριμένο περιεχόμενο της **OuterClass**. Γενικότερα,​ ισχύουν οι κανόνες για τους προσδιοριστές πρόσβασης όπως αυτοί αναφέρονται [[java:​access_modifiers|εδώ]]. +
- +
-<​code>​ +
-TestOuterClass.java:​6:​ error: OuterClass.InnerOuterClass has private access in OuterClass +
-    OuterClass.InnerOuterClass inner = outer.new InnerOuterClass(4);​ +
-              ^ +
-TestOuterClass.java:​6:​ error: OuterClass.InnerOuterClass has private access in OuterClass +
-    OuterClass.InnerOuterClass inner = outer.new InnerOuterClass(4);​ +
-</​code>​ +
- +
-<WRAP tip 80% center round> +
-Το παραπάνω εξηγεί ικανοποιήτικα τον λόγο που πιθανόν να θέλετε να βάλετε ένα "​περιοριστικό"​ προσδιοριστή πρόσβασης σε μία εσωτερική κλάση (συνήθως //​private//​). Ο λόγος είναι ότι με τον εν λόγο προσδιοριστή πρόσβασης αποκλείετε τη πρόσβαση στην εσωτερική κλάση από άλλες κλάσεις,​ κρύβοντας τον κώδικα που αφορά μόνο την συγκεκριμένη εξωτερική κλάση. +
-</​WRAP>​ +
- +
- +
-===== Πρόσβαση από την εξωτερική κλάση σε πεδία της εσωτερικής κλάσης ===== +
- +
-Το παρακάτω παράδειγμα εξηγεί την προσβασιμότητα της εξωτερικής κλάσης σε πεδία και μεθόδους της εσωτερικής κλάσης. +
- +
-<code java OuterClass.java>​ +
-public class OuterClass { +
-  int outer; +
-  InnerOuterClass innerObj; +
- +
-  public OuterClass(int o) { +
-    outer = o; +
-    innerObj = new InnerOuterClass();​ +
-    innerObj.innerPriv = 10; +
-    innerObj.innerProt = 20; +
-    innerObj.innerPub = 30; +
-    System.out.println(innerObj);​ +
-  } +
- +
-  class InnerOuterClass { +
-    private int innerPriv;​ +
-    protected int innerProt;​ +
-    public int innerPub; +
-     +
-    private void setInnerPriv(int priv) { innerPriv = priv; } +
-    protected void setInnerProt(int prot) { innerProt = prot; } +
-    public void setInnerPub(int pub) { innerPub = pub; } +
-    public String toString() {  +
-      return "​innerPriv:​ "​+innerPriv+",​ innerProt: "​+innerProt+ +
-             ",​ innerPub: "​+innerPub;​  +
-    }     +
-  } +
-   +
-  public static void main(String[] args) { +
-    OuterClass outer = new OuterClass(5);​ +
-  }   +
-+
-</​code>​ +
- +
-Όπως θα παρατηρήσετε αν μεταγλωττίσετε και εκτελέσετε το πρόγραμμα η εξωτερική κλάση έχει απόλυτη πρόσβαση στα πεδία και τις μεθόδους της εσωτερικής κλάσης ανεξάρτητα από τον προσδιοριστή πρόσβασης που έχουν αυτά. +
- +
-<WRAP important 80% center round> +
-  * H εσωτερική κλάση έχει πρόσβαση σε όλα τα πεδία της εξωτερικής κλάσης. +
-  * Η εξωτερική κλάση έχει πρόσβαση σε όλα τα πεδία της εσωτερικής κλάσης. +
-Οι προσδιοριστές πρόσβασης **δεν εισάγουν κανένα περιορισμό** στον προσδιορισμό της προσβασιμότητας των πεδίων και των μεθόδων της εσωτερικής κλάσης από την εξωτερική κλάση και αντίστροφα. +
-</​WRAP>​ +
- +
-===== Πρόσβαση από άλλες κλάσεις σε πεδία της εσωτερικής κλάσης ===== +
- +
-Ο παρακάτω κώδικας επιχειρεί να δημιουργήσει ένα αντικείμενο μίας κλάσης εσωτερικής κλάσης από μία άλλη κλάση. Σε αυτή την περίπτωση και __με την προϋπόθεση ότι η εσωτερική κλάση είναι συνολικά προσβάσιμη__ από την κλάση που επιχειρεί να δημιουργήσει το αντικείμενο,​ η προσβασιμότητα των πεδίων και των μεθόδων της εσωτερικής κλάσης εξαρτάται από τους προσδιοριστές πρόσβασης που έχουν αυτά. Δείτε το παρακάτω παράδειγμα. +
- +
-<code java OuterClass.java>​ +
-public class OuterClass { +
-  int outer; +
-  InnerOuterClass innerObj; +
- +
-  public OuterClass(int o) { +
-    outer = o; +
-    innerObj = new InnerOuterClass();​ +
-    innerObj.innerPriv = 10; +
-    innerObj.innerProt = 20; +
-    innerObj.innerPub = 30; +
-    System.out.println(innerObj);​ +
-  } +
- +
-  class InnerOuterClass { +
-    private int innerPriv;​ +
-    protected int innerProt;​ +
-    public int innerPub; +
-     +
-    private void setInnerPriv(int priv) { innerPriv = priv; } +
-    protected void setInnerProt(int prot) { innerProt = prot; } +
-    public void setInnerPub(int pub) { innerPub = pub; } +
-    public String toString() {  +
-      return "​innerPriv:​ "​+innerPriv+",​ innerProt: "​+innerProt+ +
-             ",​ innerPub: "​+innerPub;​  +
-    }     +
-  } +
-   +
-+
-</​code>​ +
- +
-<code java TestOuterClass.java>​ +
-public class TestOuterClass { +
-  public static void main(String args[]) { +
-    OuterClass outer = new OuterClass(5);​ +
-    OuterClass.InnerOuterClass inner = outer.new InnerOuterClass();​ +
-    inner.setInnerPriv(4);​ +
-    inner.setInnerProt(5);​ +
-    inner.setInnerPub(6);​ +
-    System.out.println(inner); ​   ​+
   }   }
 } }
 </​code>​ </​code>​
  
-Κατά συνέπεια, για την προσβασιμότητα των πεδίων και των μεθόδων της εσωτερικής κλάσης από μία άλλη κλάση ισχύουν ​τα εξής: +Το παρακάτω πρόγραμμα ​επιχειρεί να δημιουργήσει μία λίστα με 20 τυχαίους αριθμούς ​και ​στη συνέχεια να τους εκτυπώσει χρησιμοποιώντας τον Iterator της λίστας. Παρατηρήστε ​ότι ​σε κανένα ​σημείου του προγράμματος δεν αποκαλύπτεται η ύπαρξη των ​εσωτερικών κλάσεων //​Node// ​και ​//​Iterator//​. Η μεταβλητή //it// παραπέμπει στο interface [[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​util/​Iterator.html|java.util.Iterator]].
-  - Η εσωτερική κλάση θα πρέπει ​να είναι συνολικά ​προσβάσιμη. +
-  - Με την προϋπόθεση ότι ισχύει το **1.**, ​ισχύουν οι [[java:​access_modifiers|κανόνες προσβασιμότητας]] που ορίζονται ​από προσδιοριστές πρόσβασης.+
  
  
 +|Προηγούμενο:​ [[ :​java:​nested_classes| Εμφωλευμένες κλάσεις ​ ]] |  [[:​toc|Περιεχόμενα]] ​ |Επόμενο:​ [[ :​java:​inner_class_objects | Δημιουργία αντικειμένων της εσωτερικής κλάσης ]]|
java/inner_classes.1455197152.txt.gz · Last modified: 2016/02/26 11:15 (external edit)