JAVA ΕΝΟΤΗΤΑ 33 – ABSTRACT CLASSES, ABSTRACT METHODS AND SEALED CLASSES

 

Δωρεάν μαθήματα Java

ΕΙΣΑΓΩΓΗ

Στο σημερινό δωρεάν μάθημα Java θα εξηγήσουμε τι είναι οι Abstract classes και που μας χρησιμεύουν, πως ορίζονται τα Abstract methods, και τι ακριβώς είναι οι Sealed classes που έχουν προστεθεί στη Java 17.

Abstract Classes

Σαν abstract κλάση ορίζουμε εκείνη την κλάση από την οποία δεν μπορούμε να δημιουργήσουμε αντικείμενο. Ο μόνος τρόπος για να μπορέσουμε να το πετύχουμε αυτό είναι να κληρονομήσουμε την abstract κλάση και να κάνουμε αντικείμενο από την δική μας κλάση. Φυσικά, αφού υπάρχει κληρονομικότητα, έχουμε πρόσβαση σε όλες τις μεταβλητές, μεθόδους και constructors της abstract κλάσης. Απλά δεν μπορούμε να δημιουργήσουμε αντικείμενο άμεσα από την κλάση.

Σε ποιες περιπτώσεις όμως είναι χρήσιμος ένας τέτοιος περιορισμός? Για να καταλάβουμε την χρησιμότητα της abstract κλάσης, θα χρειαστεί να γυρίσουμε πίσω στην απλή εφαρμογή μας με τον Employee και να παρατηρήσουμε ποιο προσεχτικά την δομή των κλάσεων.

Employee.java

package com.example;

public class Employee {

    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public Employee(String name, int AFM) {
        this.name = name;
        this.AFM = AFM;
        System.out.println("Employee constructor");
    }

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }
}


Salary.java

package com.example;

public class Salary extends Employee {

    public Salary(String name, int AFM) {
        super(name, AFM);
        System.out.println("Salary Constructor");
    }

    public float salary;

    public double payment() {
        return this.salary / 52.0;
    }
}


Αυτό που παρατηρούμε είναι ότι ένα αντικείμενο είδος Employee δεν έχει όλες τις πληροφορίες που χρειαζόμαστε. Η κλάση Salary έρχεται να ολοκληρώσει την πληροφορία προσθέτοντας τον μισθό του Employee. Με άλλα λόγια, δημιουργώντας ένα αντικείμενο είδος Employee δεν μας είναι και πολύ χρήσιμο γιατί δεν θα περιέχει τον μισθό. Επίσης, μην ξεχνάτε ότι δεν μπορούμε να χρησιμοποιήσουμε τον πολυμορφισμό για να μετατρέψουμε αντικείμενα Employee σε αντικείμενα Salary. Ο πολυμορφισμός ισχύει από την κλάση που δημιουργούμε το αντικείμενο και προς τα πάνω - δηλαδή προς την κλάση Object. Αν όμως δημιουργήσουμε ένα αντικείμενο είδος Salary τότε μπορούμε να το χειριστούμε σαν Salary, σαν Employee ή ακόμα σαν Object. Το συμπέρασμα από αυτή τη μικρή ανάλυση είναι ότι κλάσεις που είναι πολύ γενικές και δεν προσφέρουν ολοκληρωμένες πληροφορίες καλό θα ήταν να είναι abstract έτσι ώστε να μην υπάρχουν αντικείμενα δημιουργημένα από αυτές. Αν και εφόσον χρειαζόμαστε τις πληροφορίες που προσφέρουν, τότε μπορούμε να δημιουργήσουμε δικές μας κλάσεις που κληρονομούν από τις abstract κλάσεις και αφού προσθέσουμε τις επιπλέον πληροφορίες που χρειαζόμαστε να δημιουργήσουμε τα αντικείμενα μας.

Πως δημιουργούμε μια abstract κλάση? Το μόνο που έχουμε να κάνουμε είναι να προσθέσουμε την λέξη abstract πριν από την λέξη class στον ορισμό της κλάσης. Όταν προσθέσουμε την λέξη abstract στην κλάση μας, αυτόματα σταματάει  η δυνατότητα να δημιουργούμε αντικείμενα απευθείας από αυτή την κλάση. Ο μόνος τρόπος είναι να δημιουργήσουμε αντικείμενα από μια άλλη κλάση που κληρονομεί από την Abstract κλάση.

Employee.java

package com.example;

public abstract class Employee {

    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public Employee(String name, int AFM) {
        this.name = name;
        this.AFM = AFM;
        System.out.println("Employee constructor");
    }

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }
}


Ο κώδικας της κλάσης Salary δεν επηρεάζεται καθόλου και παραμένει ο ίδιος όπως τον έχουμε γράψει παραπάνω. Παρατηρήστε ότι από την στιγμή που δημιουργούμε ένα αντικείμενο είδος Salary έχουμε πρόσβαση σε όλες τις ιδιότητες, χαρακτηριστικά και constructor της κλάσης Employee.

EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Salary m1 = new Salary("Michail", 12345678);
        m1.department = "Development";
        m1.salary = 20000;
        System.out.println("Hello " + m1.name);
        System.out.println("Your payment is: " + m1.payment());
    }
}


Output

Employee constructor

Salary Constructor

Hello Michail

Your payment is: 384.61538461538464

Αυτός είναι ο ορισμός της Abstract κλάσης. Αν προσπαθήσετε από περιέργεια να δημιουργήσετε αντικείμενο απευθείας από την Employee το πρόγραμμα σας θα αποτύχει.  Όμως μια abstract κλάση μπορεί να περιέχει μια ή και περισσότερες abstract μεθόδους. Τι είναι μια abstract μέθοδος?

Abstract Methods

Ας γυρίσουμε πίσω πάλι στον κώδικα μας και ας κάνουμε μια ακόμα παρατήρηση. Η κλάση Salary κληρονομεί από την κλάση Employee κάποιες μεθόδους και μεταβλητές αλλά όχι την μέθοδο payment( ). Αυτή την πρόσθεσε η κλάση Salary για να κάνει ποιο ουσιαστικό και χρήσιμο το αντικείμενο Employee. Επίσης, αν θυμάστε από προηγούμενες ενότητες, δεν έχουμε μόνο την κλάση Salary αλλά και την κλάση Hourly που κληρονομεί από την Employee και που υπολογίζει τον μισθό ενός εξωτερικού συνεργάτη ανάλογα με τις ώρες που δούλεψε. Και στις δύο περιπτώσεις αφήσαμε την λογική αυτή να είναι προαιρετική – δηλαδή η κλάση Salary και η κλάση Hourly θεώρησαν χρήσιμο να προσθέσουν την μέθοδο payment( ) στο κώδικα τους. Δεν τους το απαίτησε κανείς. Θα μπορούσε ένας από αυτούς ή και οι δύο να είχαν θεωρήσει σημαντική κάποια άλλη πληροφορία και όχι τον μισθό. Το επιθυμητό θα ήταν η κλάση Employee να “απαιτούσε” σε όσους κληρονομούν από αυτήν να έγραφαν κώδικα για συγκεκριμένες μεθόδους. Επειδή η υλοποίηση μπορεί να αλλάξει (π.χ Hourly ή Salary) θα πρέπει η Employee να περάσει απλώς την γενική δομή της μεθόδου (signature) και όποια κλάση κληρονομεί από την Employee να κάνει override την συγκεκριμένη μέθοδο (ή μεθόδους) με τον δικό της κώδικα. Αυτή η διαδικασία όμως δεν θα πρέπει να είναι προαιρετική. Με άλλα λόγια, θα πρέπει αναγκαστικά η κλάση που κληρονομεί από την Employee να κάνει override την μέθοδο ή τις μεθόδους που απαιτεί η Employee. Ο τρόπος λοιπόν που μπορούμε να απαιτήσουμε μια τέτοια διαδικασία είναι να δημιουργήσουμε abstract μεθόδους.

Πως ορίζουμε μια abstract μέθοδο? Απλά γράφουμε το signature της μεθόδου χωρίς τα άγκιστρα και αφού έχουμε προσθέσει την λέξη abstract πριν από το return type της μεθόδου. Ας δούμε λοιπόν πως αλλάζει ο κώδικας της Employee αφού προσθέσουμε την abstract μέθοδο payment( ).

Employee.java

package com.example;

public abstract class Employee {

    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public Employee(String name, int AFM) {
        this.name = name;
        this.AFM = AFM;
        System.out.println("Employee constructor");
    }

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }

    public abstract double payment();

}


Έχουμε ορίσει λοιπόν μια μέθοδο payment( ) που επιστρέφει double και είναι abstract. Ξανά, παρατηρήστε ότι λείπουν τα άγκιστρα. Η κλάση ή οι κλάσεις που θα κληρονομήσουν από την Employee θα πρέπει αναγκαστικά να κάνουν override την μέθοδο payment( ) και να γράψουν κώδικα για αυτήν. Αν δεν το κάνουν, τότε θα πρέπει να δηλώσουν και αυτές τον εαυτό τους σαν abstract που σημαίνει ότι ούτε από αυτές τις κλάσεις θα μπορέσουμε να κάνουμε αντικείμενο. Λογικά, κάποιος από όλο το δέντρο της κληρονομικότητας θα γράψει τον κώδικα της μεθόδου και θα μπορούμε έτσι να δημιουργήσουμε αντικείμενο.

Στο παράδειγμα μας, η κλάση Salary ήδη περιέχει κώδικα για την μέθοδο payment αλλά αυτό δεν είναι προαιρετικό πια. Πρέπει να γράψει τον κώδικα για την payment αφού κληρονομεί από την Employee. Αν τώρα υπήρχαν περισσότερες μέθοδοι στην Employee η Salary θα έπρεπε να τις κάνει override όλες. Αν δεν το κάνει έστω και για μια μέθοδο τότε θα πρέπει να δηλώσει και αυτή τον εαυτό της σαν abstract.

Salary.java

package com.example;

public class Salary extends Employee {

    public Salary(String name, int AFM) {
        super(name, AFM);
        System.out.println("Salary Constructor");
    }

    public float salary;

    public double payment() {
        return this.salary / 52.0;
    }
}


EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Salary m1 = new Salary("Michail", 12345678);
        m1.department = "Development";
        m1.salary = 20000;
        System.out.println("Hello " + m1.name);
        System.out.println("Your payment is: " + m1.payment());
    }
}


Output

Employee constructor

Salary Constructor

Hello Michail

Your payment is: 384.61538461538464

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

1. Μπορούμε να έχουμε abstract κλάσεις χωρίς να περιέχουν abstract μεθόδους

2. Όποια κλάση κληρονομεί από μια abstract μέθοδο θα πρέπει να γράψει κώδικα για τις abstract μεθόδους που κληρονομεί ειδάλλως πρέπει να δηλωθεί και αυτή σαν abstract.

3. Δεν μπορούμε να δημιουργήσουμε αντικείμενα από μια abstract κλάση. Αλλά μπορούμε να έχουμε πρόσβαση στις μεθόδους μιας abstract κλάσης λόγω πολυμορφισμού.

4. Μια abstract κλάση δεν μπορεί να δηλωθεί σαν final. Γιατί δεν θα μπορούμε ούτε να δημιουργήσουμε αντικείμενα αλλά ούτε και να κληρονομήσουμε από αυτήν.

5. Μια abstract κλάση δεν μπορεί να έχει δηλώσει σαν private όλους τους constructors της. Ειδάλλως δεν θα μπορούμε να κληρονομήσουμε.

6. Μια abstract μέθοδο δεν μπορεί να δηλωθεί σαν static

7. Μια abstract μέθοδο δεν μπορεί να δηλωθεί σαν private.

Υπάρχουν και κάποιες άλλες περιπτώσεις οι οποίες είναι πολύ εξειδικευμένες και νομίζω ότι δεν θα τις αντιμετωπίσετε ποτέ. Για παράδειγμα, ενώ κληρονομείτε από μια κλάση μια κανονική μέθοδο, εσείς την κάνετε override με μια abstract μέθοδο. Αυτό σημαίνει όποιος κληρονομήσει από εσάς θα πρέπει αναγκαστικά να γράψει κώδικα για αυτήν την μέθοδο.

Νομίζω ότι καλύψαμε αρκετά γύρω από την έννοια του abstraction και είμαι σίγουρος ότι σιγά σιγά θα δείτε την χρησιμότητα του ειδικότερα όταν κάνετε software design.

Sealed Class

Πριν κλείσουμε το σημερινό δωρεάν μάθημα Java, ας αναφερθούμε και σε μια καινούργια ιδιότητα που προστέθηκε στην Java 17 και ονομάζεται Sealed Class. Με την ιδιότητα της Sealed Class μπορούμε να ορίσουμε σε ποιες κλάσεις επιτρέπουμε να κληρονομήσουν από την κλάση που έχουμε ορίσει σαν Sealed. Οπότε σε συνδυασμό με την abstract ιδιότητα, αν συνδυάσουμε και τις δύο ιδιότητες τότε δεν επιτρέπουμε άμεση δημιουργία αντικειμένου, αλλά μόνο μέσω κληρονομικότητας, και ταυτόχρονα θα πρέπει να επιτρέπεται στην συγκεκριμένη κλάση να κληρονομήσει από την abstract.

Για να δηλώσουμε μια κλάση σαν Sealed, πρέπει να χρησιμοποιήσουμε την λέξη sealed αμέσως μετά από την abstract και να προσθέσουμε επίσης τη λίστα με τις κλάσεις που επιτρέπονται να μας κληρονομήσουν με την λέξη permits. Τέλος, η κλάση που θα κληρονομήσει θα πρέπει να δηλωθεί σαν non-sealed.

Employee.java

package com.example;

public abstract sealed class Employee permits Salary {

    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public Employee(String name, int AFM) {
        this.name = name;
        this.AFM = AFM;
        System.out.println("Employee constructor");
    }

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }

    public abstract double payment();

}


Salary.java

package com.example;

public non-sealed class Salary extends Employee {

    public Salary(String name, int AFM) {
        super(name, AFM);
        System.out.println("Salary Constructor");
    }

    public float salary;

    public double payment() {
        return this.salary / 52.0;
    }
}


EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Salary m1 = new Salary("Michail", 12345678);
        m1.department = "Development";
        m1.salary = 20000;
        System.out.println("Hello " + m1.name);
        System.out.println("Your payment is: " + m1.payment());
    }
}


Output

Employee constructor

Salary Constructor

Hello Michail

Your payment is: 384.61538461538464

Μην ξεχάσετε να κάνετε ένα μικρό donation έτσι ώστε αυτό το blog να μεγαλώσει ακόμα πιο πολύ και να έχει περισσότερες δυνατότητες στην online παράδοση δωρεάν μαθημάτων.

full-width

Post a Comment

0 Comments