Ορισμός κλάσεων
- Η δήλωση μιας κλάσης
τυπικά αποτελείται από τον προσδιοριστή public,
το όνομά της και ορισμό των μελών της
μέσα σε αγκύλες.
και τις μεθόδους.
public class Point {
/* Fields are defined here */
}
- Ο προδιοριστής public κάνει την κλάση μας ορατή σε κλάσεις που
έχουν οριστεί σε διαφορετικά αρχεία
- Το όνομα του αρχείου της κλάσης πρέπει να ταυτίζεται με το όνομα της
κλάσης
- Στη Java προτρέπουμε το όνομα της κλάσης να αρχίζει με κεφαλαίο γράμμα
Ορισμός πεδίων υπόστασης
- Τα πεδία υπόστασης ορίζονται σαν να ήταν μεταβλητές,
μέσα στο σώμα της κλάσης.
class Point {
/* Fields */
/** Point x coordinate */
private int x;
/** Point y coordinate */
private int y;
}
- Καλό είναι τα πεδία να ορίζονται με τον προσδιοριστή
private έτσι ώστε να μην είναι ορατά σε άλλες κλάσεις.
- Στη Java προτρέπουμε τα ονόματα των πεδίων να αρχίζουν με πεζό γράμμα
Ορισμός μεθόδων υπόστασης
- Οι μέθοδοι υπόστασης ορίζονται ως συναρτήσεις
μέσα στο σώμα της κλάσης (χωρίς προσδιοριστή static).
class Point {
/* Fields */
/** Point x coordinate */
private int x;
/** Point y coordinate */
private int y;
/* Methods */
/** Return the x coordinate */
public int getX() { return x; }
/** Return the y coordinate */
public int getY() { return y; }
/** Set the point position to the specified coordinates */
public void setPosition(int sx, int sy) {
x = sx;
y = sy;
}
/** Return string representation */
@Override public String toString() {
return "x=" + Integer.valueOf(x).toString() +
" y=" + Integer.valueOf(y).toString();
}
}
- Στη Java προτρέπουμε τα ονόματα των μεθόδων να αρχίζουν με πεζό γράμμα
Πρόσβαση στα μέλη της κλάσης
- Μέσα στο σώμα των μεθόδων υπόστασης της κλάσης έχουμε άμεση πρόσβαση
σε όλες τις μεθόδους και τα πεδία με απλή χρήση του ονόματός τους
- Όταν αναφερόμαστε σε μια μέθοδο ή πεδίο εξυπακούεται πως αναφερόμαστε
στο αντικείμενο για το οποίο κλήθηκε η δική μας μέθοδος
- Στο αντικείμενο αυτό μπορούμε να αναφερθούμε και με το όνομα
this.
- Από μεθόδους κλάσης (static) δε μπορούμε να έχουμε άμεση
πρόσβαση σε μεθόδους
και πεδία υπόστασης μια και οι μέθοδοι κλάσης δεν καλούνται για ένα
συγκεκριμένο αντικείμενο.
Πρόσβαση στα μέλη της κλάσης: παράδειγμα
- Έτσι οι ο ορισμός
class Point {
/** Set the point position to the specified coordinates */
public void setPosition(int sx, int sy) {
x = sx;
y = sy;
}
}
είναι ίδιος με τον ορισμό
class Point {
/** Set the point position to the specified coordinates */
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
}
Μέθοδοι κατασκευής
- Σε κάθε κλάση μπορούν να οριστούν
μέθοδοι κατασκευής με όνομα ίδιο
με αυτό της κλάσης.
-
Η μέθοδος κατασκευής καλείται κάθε φορά που δημιουργείται
ένα νέο αντικείμενο (με τη χρήση new ObjectType).
-
Το όρισμα που δηλώνουμε στη μέθοδο κατασκευής επιτρέπει προσδιορισμό
ιδιοτήτων του αντικειμένου που δημιουργούμε (π.χ. τον αριθμό στοιχείων
σε έναν πίνακα) ή αρχικών τιμών.
- Για το ίδιο αντικείμενο μπορούμε να ορίσουμε πολλές μεθόδους κατασκευής
με διαφορετικά ορίσματα.
- Η μέθοδος κατασκευής δε χρειάζεται κάποιον
προσδιοριστή (static, public, void) στον ορισμό της.
- Ρόλος της μεθόδου κατασκευής είναι συνήθως να δίνει αρχικές τιμές
στα πεδία της κλάσης και να αποκτά πρόσβαση σε εξωτερικούς πόρους που
απαιτούνται για τη λειτουργία της.
Μέθοδοι κατασκευής: παράδειγμα
class Point {
/* Fields */
/** Point x coordinate */
private int x;
/** Point y coordinate */
private int y;
/* Methods */
/** Default constructor */
Point() { x = y = 0; }
/** Constructor with coordinates */
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Έλεγχος πρόσβασης
Κάθε μέθοδος ή πεδίο μπορεί να έχει ως προσδιοριστή
- public
- Το μέλος είναι ορατό σε όλες τις άλλες κλάσεις
- private
- Το μέλος είναι ορατό μόνο στις μεθόδους της δικής μας κλάσης
- (τίποτα)
- Το μέλος είναι ορατό στις κλάσεις του πακέτου μας
Μια καλοσχεδιασμένη κλάση έχει:
- Όλα τα μέλη υπόστασης ορισμένα ώς private.
- Όλες τις μεθόδους που δεν έχουν σχεδιαστεί για χρήση έξω από
την κλάση ορισμένες ως private.
Ορισμός ιδιοτήτων και μεθόδων κλάσης
-
Σε μια κλάση μπορούν να δηλωθούν (με τον προσδιορισμό static)
πεδία τα οποία υπάρχουν μόνο μια φορά για όλη την κλάση,
καθώς και αντίστοιχες μέθοδοι.
-
Τα μέλη αυτά χρησιμοποιούνται για την επεξεργασία στοιχείων
που αφορούν ολόκληρη την κλάση και όχι τα αντικείμενά της.
-
Οι συναρτήσεις που έχουν οριστεί static δεν έχουν πρόσβαση σε μη
static μεταβλητές ούτε στη μεταβλητή this.
Παράδειγμα ιδιοτήτων και μεθόδων κλάσης
-
Το παρακάτω παράδειγμα ορίζει έναν μετρητή numPoints που μετρά πόσα
σημεία είναι ενεργά καθώς και την αντίστοιχη συνάρτηση πρόσβασης getNumPoints:
class Point {
private int x, y;
/** Count number of points used */
private static int numPoints = 0;
/** Point constructor */
Point(int ix, int iy) {
x = ix;
y = iy;
numPoints++; // Adjust points counter
}
/** Point constructor */
Point() {
x = y = 0;
numPoints++; // Adjust points counter
}
// Return number of points currently used
public static int getNumPoints() {
return numPoints;
}
static public void main(String args[]) {
Point a = new Point(1, 2);
Point b = new Point(5, 8);
Point c = b;
System.out.println(getNumPoints() + " points have been created");
}
}
Μπλοκ αρχικοποίησης
Σε ανώνυμα μπλοκ
{ } στο σώμα της κλάσης μπορεί να τοποθετηθεί:
- κώδικας που αρχικοποιεί όλη την κλάση (με τον προσδιοριστή static),
- κώδικας που αρχικοποιεί κάθε αντικείμενο (χωρίς προσδιοριστή).
- Τα μπλοκ αρχικοποίησης αντικειμένων καλούνται πριν τους κατασκευαστές.
Μπλοκ αρχικοποίησης: παράδειγμα
import java.util.concurrent.ThreadLocalRandom;
class IdentifiedPoint {
private double x, y;
/** Unique point identifier */
int identifier;
/** Provide an identifier for each point */
private static int nextIdentifier;
/** Class initialization block */
static {
nextIdentifier = ThreadLocalRandom.current().nextInt();
}
/** Instance initialization block */
{
identifier = nextIdentifier++;
}
/** Point constructor */
IdentifiedPoint(int ix, int iy) {
x = ix;
y = iy;
}
/** Point constructor */
IdentifiedPoint() {
x = y = 0;
}
static public void main(String args[]) {
var a = new IdentifiedPoint(1, 2);
var b = new IdentifiedPoint();
System.out.println(a.identifier);
System.out.println(b.identifier);
}
}
Εσωτερικές κλάσεις
Μια κλάση μπορεί να οριστεί εσωτερικά:
- στο σώμα μιας κλάσης, με προσδιοριστή:
- public ώστε να είναι ορατή έξω από αυτή
- private ώστε να μην είναι ορατή έξω από αυτή
- static όταν δεν απαιτεί πρόσβαση στα πεδία υπόστασης
- μέσα στο σώμα μιας μεθόδου
- ανώνυμα, ως επέκταση του κατασκευαστή μιας υπάρχουσας κλάσης ή διεπαφής
Παράδειγμα εσωτερικής κλάσης
class Rectangle {
private static class Point {
private int x, y;
/** Point constructor */
Point(int ix, int iy) {
x = ix;
y = iy;
}
}
private Point topLeft, bottomRight;
/** Rectangle constructor */
Rectangle(int x, int y, int height, int width) {
topLeft = new Point(x, y);
bottomRight = new Point(x + width, y + height);
}
}
Παράδειγμα ανώνυμης κλάσης
class InnerCall {
public static void main(String args[]) {
System.out.println(new Object());
System.out.println(new Object() {
@Override
public String toString() {
return "I am a woke object";
}
});
}
}
Απαριθμήσεις
- Η απαρίθμηση (enumeration) είναι ένας
ειδικός τύπος κλάσης που ορίζει με συμβολικό τρόπο σταθερές
τιμές.
- Οι τιμές ορίζονται (ως στατικά μέλη της κλάσης) χωρισμένες με κόμμα.
Παράδειγμα
enum Ingredients {
TOMATO,
ONION,
TZATZIKI,
POTATO,
MUSTARD,
SOUVLAKI,
GYROS
};
Χρήση απαριθμήσεων
- Κάθε στοιχείο της απαρίθμησης μπορεί να χρησιμοποιηθεί σαν
να είχε οριστεί ως
public final μέλος της κλάσης.
- Οι τιμές μπορούν να χρησιμοποιηθούν ως τιμή σε
case,
π.χ. case Ingredients.POTATO:.
- Η μέθοδος
int ordinal() επιστρέφει τη θέση ενός στοιχείου στην απαρίθμηση.
- Η μέθοδος
String name() επιστρέφει το όνομα ενός στοιχείου.
- Η στατική μέθοδος
valueOf επιστρέφει την τιμή της απαρίθμησης με βάση συμβολοσειρά με το όνομά της, π.χ. Enum.valueOf(Ingredients.class, "TOMATO").
- Η στατική μέθοδος
values επιστρέφει έναν πίνακα με τις τιμές μιας απαρίθμησης.
Απαρίθμηση ως κλάση
Η αντίστοιχη κλάση χωρίς τη χρήση απαρίθμησης θα είχε την παρακάτω μορφή.
public class Ingredients {
// Static array to hold all ingredient constants
private static final Ingredients[] VALUES = new Ingredients[7];
// Constants with names and ordinal values
public static final Ingredients TOMATO = new Ingredients("TOMATO", 0);
public static final Ingredients ONION = new Ingredients("ONION", 1);
public static final Ingredients TZATZIKI = new Ingredients("TZATZIKI", 2);
public static final Ingredients POTATO = new Ingredients("POTATO", 3);
public static final Ingredients MUSTARD = new Ingredients("MUSTARD", 4);
public static final Ingredients SOUVLAKI = new Ingredients("SOUVLAKI", 5);
public static final Ingredients GYROS = new Ingredients("GYROS", 6);
private final String name;
private final int ordinal;
private Ingredients(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
VALUES[ordinal] = this; // Add to the static array
}
// Getters
public int ordinal() {
return ordinal;
}
public String getName() {
return name;
}
public static Ingredients[] values() {
return VALUES.clone(); // Return a copy of the array
}
public static Ingredients valueOf(String name) {
for (Ingredients ingredient : VALUES) {
if (ingredient.name.equals(name)) {
return ingredient;
}
}
throw new IllegalArgumentException("No enum constant " + name);
}
@Override
public String toString() {
return name;
}
}
Πολυμορφισμός με ταίριαγμα προτύπων
Με εντολές
switch μπορούμε να
γράψουμε κώδικα για συγκεκριμένους τύπους με
βάση το
ταίριασμα προτύπων (pattern matching).
class PaternMatch {
class RetailCustomer {}
class BusinessCustomer {}
/** Return the document a given object shall receive */
private static String documentName(Object o) {
return switch (o) {
case RetailCustomer rc -> "receipt";
case BusinessCustomer bc -> "invoice";
default -> "unknown document";
};
}
public static void main(String[] args) {
var retailCustomer = new RetailCustomer();
var businessCustomer = new BusinessCustomer();
System.out.println("Retail customer gets "
+ documentName( retailCustomer));
System.out.println("Business customer gets "
+ documentName(businessCustomer));
}
}
Εγγραφές
-
Η εγγραφή (record) επιτρέπει τον εύκολο ορισμό
αμετάβλητων (immutable) αντικειμένων.
Αυτά δεν εκφράζουν σύνθετη συμπεριφορά, αλλά κυρίως σύνθεση δεδομένων.
-
Η εγγραφές ορίζοuν αυτόματα κατασκευαστές καθώς και μεθόδους
πρόσβασης στις ιδιότητες, εμφάνισης και ισότητας.
-
Η εγγραφές μπορούν να επεκταθούν με πρόσθετες μεθόδους.
Παράδειγμα
public class RecordDemo {
record Point(int x, int y) {}
public static void main(String[] args) {
var a = new Point(5, 12); // Pythagorean triple
System.out.println("Point " + a + " has magnitude "
+ Math.sqrt(a.x() * a.x() + a.y() * a.y()));
}
}
Εγγραφή ως κλάση
Η αντίστοιχη κλάση χωρίς τη χρήση εγγραφής θα είχε την παρακάτω μορφή.
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// Getters
public int getX() {
return x;
}
public int getY() {
return y;
}
// Object methods
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point{" + "x=" + x + ", y=" + y + '}';
}
}
Ταίριαγμα προτύπων σε εγγραφές
Σε εγγραφές μπορούμε να ταιριάξουμε και τα ορίσματά τους.
class PaternMatch {
record Point2D(double x, double y) {}
record Point3D(double x, double y, double z) {}
/** Output the magnitude of the specified object */
private static void printMagnitude(Object o) {
System.out.println("Magnitude of " + o + " is " +
switch (o) {
case Point2D(double x, double y) -> Math.sqrt(x * x + y * y);
case Point3D(double x, double y, double z) ->
Math.sqrt(x * x + y * y + z * z);
default -> throw new IllegalArgumentException("Unexpected type: "
+ o.getClass().getName());
}
);
}
public static void main(String[] args) {
printMagnitude(new Point2D(3, 4)); // Pythagorean triple
printMagnitude(new Point3D(1, 4, 8)); // Pythagorean quadruple
}
}
Το σχεδιαστικό πρότυπο singleton
- Πολλές κλάσεις αντιπροσωπεύουν αντικείμενα από τα οποία μπορεί να υπάρξει μόνο ένα.
- Μερικοί παριστάνουν τέτοιες κλάσεις με τη χρήση μελών κλάσης (static).
- Η λύση αυτή είναι άκομψη και δεν επιτρέπει την εύκολη αλλαγή του σχεδίου αν τελικά χρειαστούν παραπάνω αντικείμενα της κλάσης.
- Το σχεδιαστικό πρότυπο (design pattern)
singleton επιτρέπει τον ελεγχόμενο ορισμό ενός μοναδικού αντικειμένου
μιας κλάσης.
Παράδειγμα singleton
-
public class Accounts {
/** Single instance of a company's accounts. */
private static final Accounts instance = new Accounts();
/** Return the single instance of the company's accounts. */
public static Accounts getInstance() {
return instance;
}
/** Supress public default constructor to assure non-instantiation */
private final Accounts() {
// Never invoked from outside the class
}
}
- Πρόσβαση στο μοναδικό αντικείμενο Accounts έχουμε μόνο με τη μέθοδο getInstance()
Άσκηση: δημιουργία μιας κλάσης
Άσκηση 4
Μπορείτε να κατεβάσετε το αντίστοιχο αρχείο και να στείλετε τους
βαθμούς σας από τους δεσμούς που βρίσκονται στη
σελίδα των ασκήσεων.
Βιβλιογραφία
- Joyce Farrell. Java: Εκμάθηση με πρακτικά παραδείγματα. 2η έκδοση. Εκδόσεις Κριτική, Αθήνα 2024. Κεφ. 1-4.
- Rogers CadenheadΠλήρες εγχειρίδιο της Java 12Όγδοη έκδοση. Εκδόσεις Μ. Γκιούρδας, Αθήνα 2023. Κεφ. 5.
- Herbert Schildt. Οδηγός της Java 7. 5η έκδοση. Εκδόσεις Γκιούρδας Μ., Αθήνα 2012. Κεφ. 1, 2, 3.
- Harvey M. Deitel και Paul J. Deitel. Java Προγραμματισμός, 6η έκδοση. Εκδόσεις Μ. Γκιούρδας, Αθήνα 2005. Κεφάλαια 5,8
- Else Lervik και Vegard B. Havdal Java με UML. Εκδόσεις Κλειδάριθμος 2005. Κεφάλαιο 4.
- Rogers Cadenhead και Laura Lemay Πλήρες εγχειρίδιο της Java 2 Εκδόσεις Μ. Γκιούρδας, Αθήνα 2003. Κεφάλαιο 2, 5.
- Γιάννη Κάβουρα. Προγραμματισμός με Java. Εκδόσεις Κλειθάριθμος, Αθήνα 2003. Κεφάλαιο 9, 12.
- Joshua Bloch.
Effective Java, pages 10–12.
Addison-Wesley, Reading, MA, 2001.
- Erich
Gamma, Richard Helm, Ralph Johnson, and
John Vlissides.
Design Patterns: Elements of Reusable Object-Oriented Software,
pages 127–134.
Addison-Wesley, Reading, MA, 1995.
Περιεχόμενα