Φωλιασμενες Κλασεις
Η Java επιτρέπει να ορίζουμε κλάσεις μέσα σε κλάσεις όπως βλέπετε
πιο κάτω:
class OuterClass
{
...
class NestedClass {
...
}
}
Οι κλάσεις αυτές ονομάζονται φωλιασμένες (nested)
Οι φωλιασμένες κλάσεις χωρίζονται σε δύο κατηγορίες: στατικές και μη-στατικές.
Οι φωλιασμένες κλάσεις που ορίζονται static λέγονται απλά στατικές φωλιασμένες κλάσεις(static nested classes).
Οι μη-στατικές κλάσεις ονομάζονται εσωτερικές κλάσεις (inner classes).
class OuterClass {
...
static class StaticNestedClass
{
...
}
class InnerClass
{
...
}
}
Μια φωλιασμένη κλάση είναι μέλος της κλάσης που την περικλείει. Οι inner κλάσεις έχουν πρόσβαση στα μέλη της κλάσης που τις περιλαμβάνει ακόμα και αν αυτά είναι δηλωμένα private. Οι static εσωτερικές κλάσεις δεν έχουν πρόσβαση στα μέλη της εξωτερικής τους κλάσης.
Ενώ οι κανονικές κλάσεις μπορούν να οριστούν μόνο public ή package-private οι φωλιασμένες κλάσεις ορίζονται με private, public, protected, ή package private όπως τα μέλη μιας κλάσης
Γιατί χρησιμοποιούμαι φωλιασμένες κλάσεις
Λογική ομαδοποίηση: αν μια κλάση είναι χρήσιμη μόνο σε μια άλλη είναι λογικό να ενσωματωθεί η μία στην άλλη και να παραμένουν πάντα μαζί. Οι κλάσεις αυτές λέγονται και βοηθητικές και βοηθούν στο να βελτιστοποιηθεί η δομή του package.
Αυξημένη ενθυλάκωση: Για παράδειγμα έχουμε δύο κλάσεις Α και Β, όπου η Β χρειάζεται να έχει πρόσβαση στα πεδία της Α. Τα πεδία της Α πρέπει όμως να είναι δηλωμένα private. Με την ενθυλάκωση της Β εντός της Α, η Β έχει πρόσβαση στα private πεδία της Α, ενώ και η ίδια η Β μένει κρυφή.
Για την υλοποίηση ευανάγνωστου και ευσυντήρητου κώδικα: διατηρώντας τον κώδικα εσωτερικών κλάσεων εκεί που χρησιμοποιείται.
Στατικές Φωλιασμένες Κλάσεις
Οι στατικές φωλιασμένες κλάσεις δεν έχουν άμεση πρόσβαση στα πεδία της εξωτερικής κλάσης. Για να έχουν πρόσβαση σε αυτή πρέπει να το κάνουν όπως οι κανονικές κλάσεις
Εδώ βλέπουμε τον τρόπο πρόσβασης σε αυτές:
OuterClass.StaticNestedClass
Και για παράδειγμα
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
Εσωτερικές κλάσεις (Inner)
Όπως και οι μέθοδοι και τα πεδία ενός στιγμιότυπου, οι εσωτερικές κλάσεις σχετίζονται μόνο με το στιγμιότυπο που τις περικλείει και έχουν άμεση πρόσβαση στα μέλη του. Για τον ίδιο λόγο η εσωτερική κλάση δεν μπορεί να ορίζει στατικά πεδία που ανήκουν στην κλάση του στιγμιότυπου..
class OuterClass
{
...
class InnerClass
{
...
}
}
Το παρακάτω σχήμα δείχνει αυτή την επικοινωνία:
class OuterClass
{
...
class NestedClass {
...
}
}
Οι κλάσεις αυτές ονομάζονται φωλιασμένες (nested)
Οι φωλιασμένες κλάσεις χωρίζονται σε δύο κατηγορίες: στατικές και μη-στατικές.
Οι φωλιασμένες κλάσεις που ορίζονται static λέγονται απλά στατικές φωλιασμένες κλάσεις(static nested classes).
Οι μη-στατικές κλάσεις ονομάζονται εσωτερικές κλάσεις (inner classes).
class OuterClass {
...
static class StaticNestedClass
{
...
}
class InnerClass
{
...
}
}
Μια φωλιασμένη κλάση είναι μέλος της κλάσης που την περικλείει. Οι inner κλάσεις έχουν πρόσβαση στα μέλη της κλάσης που τις περιλαμβάνει ακόμα και αν αυτά είναι δηλωμένα private. Οι static εσωτερικές κλάσεις δεν έχουν πρόσβαση στα μέλη της εξωτερικής τους κλάσης.
Ενώ οι κανονικές κλάσεις μπορούν να οριστούν μόνο public ή package-private οι φωλιασμένες κλάσεις ορίζονται με private, public, protected, ή package private όπως τα μέλη μιας κλάσης
Γιατί χρησιμοποιούμαι φωλιασμένες κλάσεις
Λογική ομαδοποίηση: αν μια κλάση είναι χρήσιμη μόνο σε μια άλλη είναι λογικό να ενσωματωθεί η μία στην άλλη και να παραμένουν πάντα μαζί. Οι κλάσεις αυτές λέγονται και βοηθητικές και βοηθούν στο να βελτιστοποιηθεί η δομή του package.
Αυξημένη ενθυλάκωση: Για παράδειγμα έχουμε δύο κλάσεις Α και Β, όπου η Β χρειάζεται να έχει πρόσβαση στα πεδία της Α. Τα πεδία της Α πρέπει όμως να είναι δηλωμένα private. Με την ενθυλάκωση της Β εντός της Α, η Β έχει πρόσβαση στα private πεδία της Α, ενώ και η ίδια η Β μένει κρυφή.
Για την υλοποίηση ευανάγνωστου και ευσυντήρητου κώδικα: διατηρώντας τον κώδικα εσωτερικών κλάσεων εκεί που χρησιμοποιείται.
Στατικές Φωλιασμένες Κλάσεις
Οι στατικές φωλιασμένες κλάσεις δεν έχουν άμεση πρόσβαση στα πεδία της εξωτερικής κλάσης. Για να έχουν πρόσβαση σε αυτή πρέπει να το κάνουν όπως οι κανονικές κλάσεις
Εδώ βλέπουμε τον τρόπο πρόσβασης σε αυτές:
OuterClass.StaticNestedClass
Και για παράδειγμα
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
Εσωτερικές κλάσεις (Inner)
Όπως και οι μέθοδοι και τα πεδία ενός στιγμιότυπου, οι εσωτερικές κλάσεις σχετίζονται μόνο με το στιγμιότυπο που τις περικλείει και έχουν άμεση πρόσβαση στα μέλη του. Για τον ίδιο λόγο η εσωτερική κλάση δεν μπορεί να ορίζει στατικά πεδία που ανήκουν στην κλάση του στιγμιότυπου..
class OuterClass
{
...
class InnerClass
{
...
}
}
Το παρακάτω σχήμα δείχνει αυτή την επικοινωνία:
Για να δημιουργήσουμε στιγμιότυπο της inner κλάσης πρέπει πρώτα να δημιουργήσουμε στιγμιότυπο της εξωτερικής κλάσης. Αμέσως μετά μπορούμε να δημιουργήσουμε το εσωτερικο στιμγιότυπο έτσι:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
Εχουμε 2 είδη inner κλάσεων: τις τοπικές (local) και ανώνυμες (anonymous) που θα δούμε πιο κάτω.
Χρήσιμο blog: Nested, Inner, Member and Top-Level Classes.
Shadowing( Επισκίαση)
Στις περιπτώσεις που μια δήλωση τύπου (πεδίο, μεταβλητή ή μέθοδος) μέσα σε συγκεκριμένη περιοχή του κώδικα (όπως ορισμός inner κλάσης ή μεθόδου) έχει το ίδιο όνομα με μία δήλωση εκτός της περιοχής, τότε η εξωτερική δήλωση «επισκιάζει» την εσωτερική, και δεν μπορούμε να αναφερθούμε στην τελευταία μόνο με το όνομα της.
Δείτε το παρακάτω παράδειγμα, ShadowTest:
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
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);
}
}
public static void main(String... args)
{
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
Η έξοδος έχει ως εξής:
x = 23 (ως παράμετρος της μεθόδου methodInFirstLevel)
this.x = 1 (ως μέλος της FirstLevel)
ShadowTest.this.x = 0 (ως μέλος της ShadowTest)
Δείτε και αυτό το παράδειγμα
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
Εχουμε 2 είδη inner κλάσεων: τις τοπικές (local) και ανώνυμες (anonymous) που θα δούμε πιο κάτω.
Χρήσιμο blog: Nested, Inner, Member and Top-Level Classes.
Shadowing( Επισκίαση)
Στις περιπτώσεις που μια δήλωση τύπου (πεδίο, μεταβλητή ή μέθοδος) μέσα σε συγκεκριμένη περιοχή του κώδικα (όπως ορισμός inner κλάσης ή μεθόδου) έχει το ίδιο όνομα με μία δήλωση εκτός της περιοχής, τότε η εξωτερική δήλωση «επισκιάζει» την εσωτερική, και δεν μπορούμε να αναφερθούμε στην τελευταία μόνο με το όνομα της.
Δείτε το παρακάτω παράδειγμα, ShadowTest:
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
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);
}
}
public static void main(String... args)
{
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
Η έξοδος έχει ως εξής:
x = 23 (ως παράμετρος της μεθόδου methodInFirstLevel)
this.x = 1 (ως μέλος της FirstLevel)
ShadowTest.this.x = 0 (ως μέλος της ShadowTest)
Δείτε και αυτό το παράδειγμα
Τοπικές Κλάσεις
Οι τοπικές κλάσεις έιναι inner κλάσεις που ορίζονται μέσα σε κλειστά block κώδικα, συνήθως αυτά μεθόδων.
Το παρακάτω παράδειγμα, LocalClassExample, επιβεβαιώνει την ορθότητα 2 αριθμών τηλεφώνου. Ορίζει την τοπική κλάση PhoneNumber μέσα στην μέθοδο validatePhoneNumber:
public class LocalClassExample {
static String regularExpression = "[^0-9]";
public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) {
final int numberLength = 10;
// Valid in Java SE 8 and later:
// int numberLength = 10;
class PhoneNumber
{
String formattedPhoneNumber = null;
PhoneNumber(String phoneNumber)
{
// numberLength = 7;
String currentNumber = phoneNumber.replaceAll(regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
public String getNumber()
{
return formattedPhoneNumber;
}
// Valid in Java SE 8 and later:
// public void printOriginalNumbers() {
// System.out.println("Original numbers are " + phoneNumber1 +
// " and " + phoneNumber2);
// }
}
PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
// Valid in Java SE 8 and later:
// myNumber1.printOriginalNumbers();
if (myNumber1.getNumber() == null)
System.out.println("First number is invalid");
else
System.out.println("First number is " + myNumber1.getNumber());
if (myNumber2.getNumber() == null)
System.out.println("Second number is invalid");
else
System.out.println("Second number is " + myNumber2.getNumber());
}
public static void main(String... args)
{
validatePhoneNumber("123-456-7890", "456-7890");
}
}
Οι τοπικές κλάσεις έιναι inner κλάσεις που ορίζονται μέσα σε κλειστά block κώδικα, συνήθως αυτά μεθόδων.
Το παρακάτω παράδειγμα, LocalClassExample, επιβεβαιώνει την ορθότητα 2 αριθμών τηλεφώνου. Ορίζει την τοπική κλάση PhoneNumber μέσα στην μέθοδο validatePhoneNumber:
public class LocalClassExample {
static String regularExpression = "[^0-9]";
public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) {
final int numberLength = 10;
// Valid in Java SE 8 and later:
// int numberLength = 10;
class PhoneNumber
{
String formattedPhoneNumber = null;
PhoneNumber(String phoneNumber)
{
// numberLength = 7;
String currentNumber = phoneNumber.replaceAll(regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
public String getNumber()
{
return formattedPhoneNumber;
}
// Valid in Java SE 8 and later:
// public void printOriginalNumbers() {
// System.out.println("Original numbers are " + phoneNumber1 +
// " and " + phoneNumber2);
// }
}
PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
// Valid in Java SE 8 and later:
// myNumber1.printOriginalNumbers();
if (myNumber1.getNumber() == null)
System.out.println("First number is invalid");
else
System.out.println("First number is " + myNumber1.getNumber());
if (myNumber2.getNumber() == null)
System.out.println("Second number is invalid");
else
System.out.println("Second number is " + myNumber2.getNumber());
}
public static void main(String... args)
{
validatePhoneNumber("123-456-7890", "456-7890");
}
}
Ανώνυμες Κλάσεις
Οι ανώνυμες κλάσεις μας επιτρέπουν να κάνουμε τον κώδικα μας πιο συμπαγή. Μας δίνουν την δυνατότητα να ορίσουμε μία κλάση και να δημιουργήσουμε το στιγμιότυπο της ταυτόχρονα. Μοιάζουν με τις τοπικές, διαφέρουν όμως στο ότι δεν έχουν όνομα. Τις χρησιμοποιούμε όταν θέλουμε να χρησιμοποιήσουμε την κλάση μόνο μία φορά. Ενώ οι τοπικές κλάσεις είναι δηλώσεις κλάσης, οι ανώνυμες κλάσεις είναι εκφράσεις, δηλαδή ορίζουμε την κλάσης σε διαφορετική έκφραση. Το παρακάτω παράδειγμα HelloWorldAnonymousClasses, χρησιμοποιεί ανώνυμες κλάσεις ως δήλωση αρχικοποίησης των τοπικών μεταβλητών frenchGreeting και spanishGreeting, αλλά χρησιμοποιεί τοπικές κλάσεις για την αρχικοποίηση της englishGreeting:
public class HelloWorldAnonymousClasses
{
interface HelloWorld
{
public void greet();
public void greetSomeone(String someone); }
public void sayHello()
{
class EnglishGreeting implements HelloWorld
{
String name = "world";
public void greet()
{
greetSomeone("world");
}
public void greetSomeone(String someone)
{
name = someone;
System.out.println("Hello " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
HelloWorld frenchGreeting = new HelloWorld(){
String name = "tout le monde";
public void greet()
{
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
Οι ανώνυμες κλάσεις μας επιτρέπουν να κάνουμε τον κώδικα μας πιο συμπαγή. Μας δίνουν την δυνατότητα να ορίσουμε μία κλάση και να δημιουργήσουμε το στιγμιότυπο της ταυτόχρονα. Μοιάζουν με τις τοπικές, διαφέρουν όμως στο ότι δεν έχουν όνομα. Τις χρησιμοποιούμε όταν θέλουμε να χρησιμοποιήσουμε την κλάση μόνο μία φορά. Ενώ οι τοπικές κλάσεις είναι δηλώσεις κλάσης, οι ανώνυμες κλάσεις είναι εκφράσεις, δηλαδή ορίζουμε την κλάσης σε διαφορετική έκφραση. Το παρακάτω παράδειγμα HelloWorldAnonymousClasses, χρησιμοποιεί ανώνυμες κλάσεις ως δήλωση αρχικοποίησης των τοπικών μεταβλητών frenchGreeting και spanishGreeting, αλλά χρησιμοποιεί τοπικές κλάσεις για την αρχικοποίηση της englishGreeting:
public class HelloWorldAnonymousClasses
{
interface HelloWorld
{
public void greet();
public void greetSomeone(String someone); }
public void sayHello()
{
class EnglishGreeting implements HelloWorld
{
String name = "world";
public void greet()
{
greetSomeone("world");
}
public void greetSomeone(String someone)
{
name = someone;
System.out.println("Hello " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
HelloWorld frenchGreeting = new HelloWorld(){
String name = "tout le monde";
public void greet()
{
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
Τύπος Enum (δείτε εδώ)
Ένας τύπος enum είναι ένας ειδικός τύπος δεδομένων που μας δίνει την δυνατότητα να προκαθορίσουμε μια σειρά από σταθερές τιμές. Η μεταβλητή που ορίζουμε με αυτόν τον τύπο πρέπει να έχει μία από τις σταθερές τιμές.
Επειδή οι τιμές είναι σταθερές οι ονοματολογία τους ακολουθεί αυτή των σταθερών στην Java, δηλαδή η ΟΛΑ ΚΕΦΑΛΑΙΑ.
public enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
}
public class EnumTest {
Day day;
public EnumTest(Day day) {
this.day = day;
}
public void tellItLikeItIs() {
switch (day) {
case MONDAY:
System.out.println("Mondays are bad.");
break;
case FRIDAY:
System.out.println("Fridays are better.");
break;
case SATURDAY: case SUNDAY:
System.out.println("Weekends are best.");
break;
default:
System.out.println("Midweek days are so-so.");
break;
}
}
public static void main(String[] args) {
EnumTest firstDay = new EnumTest(Day.MONDAY);
firstDay.tellItLikeItIs();
EnumTest thirdDay = new EnumTest(Day.WEDNESDAY);
thirdDay.tellItLikeItIs();
EnumTest fifthDay = new EnumTest(Day.FRIDAY);
fifthDay.tellItLikeItIs();
EnumTest sixthDay = new EnumTest(Day.SATURDAY);
sixthDay.tellItLikeItIs();
EnumTest seventhDay = new EnumTest(Day.SUNDAY);
seventhDay.tellItLikeItIs();
}
}
Η έξοδος είναι:
Mondays are bad.
Midweek days are so-so.
Fridays are better.
Weekends are best.
Weekends are best.
Ένας τύπος enum είναι ένας ειδικός τύπος δεδομένων που μας δίνει την δυνατότητα να προκαθορίσουμε μια σειρά από σταθερές τιμές. Η μεταβλητή που ορίζουμε με αυτόν τον τύπο πρέπει να έχει μία από τις σταθερές τιμές.
Επειδή οι τιμές είναι σταθερές οι ονοματολογία τους ακολουθεί αυτή των σταθερών στην Java, δηλαδή η ΟΛΑ ΚΕΦΑΛΑΙΑ.
public enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
}
public class EnumTest {
Day day;
public EnumTest(Day day) {
this.day = day;
}
public void tellItLikeItIs() {
switch (day) {
case MONDAY:
System.out.println("Mondays are bad.");
break;
case FRIDAY:
System.out.println("Fridays are better.");
break;
case SATURDAY: case SUNDAY:
System.out.println("Weekends are best.");
break;
default:
System.out.println("Midweek days are so-so.");
break;
}
}
public static void main(String[] args) {
EnumTest firstDay = new EnumTest(Day.MONDAY);
firstDay.tellItLikeItIs();
EnumTest thirdDay = new EnumTest(Day.WEDNESDAY);
thirdDay.tellItLikeItIs();
EnumTest fifthDay = new EnumTest(Day.FRIDAY);
fifthDay.tellItLikeItIs();
EnumTest sixthDay = new EnumTest(Day.SATURDAY);
sixthDay.tellItLikeItIs();
EnumTest seventhDay = new EnumTest(Day.SUNDAY);
seventhDay.tellItLikeItIs();
}
}
Η έξοδος είναι:
Mondays are bad.
Midweek days are so-so.
Fridays are better.
Weekends are best.
Weekends are best.