ΠΛΗΡΟΦΟΡΙΚΗ Ι JAVA Τμήμα θεωρίας με Α.Μ. σε 3, 7, 8 & 9 10/1/08 Συνέχεια Αναδρομής (recursion): Ο αλγόριθμος του Ευκλείδη για τον Μέγιστο Κοινό Διαιρέτη (ΜΚΔ) με αναδρομή: p, αν q=0 (βασική περίπτωση) ΜΚΔ(p, q) = ΜΚΔ(q, p%q), διαφορετικά (αναδρομικό βήμα) Το πρόγραμμα: public static int mkde(int p, int q) if (q==0) return p; return mkde(q, p%q); Κάποια προβλήματα της αναδρομής: i) Υπολογισμός του 1 1 1 H n = 1 + + +... + (n-αρμονικός) 2 3 n public static double h(int n) if (n==1) return 1.0; return h(n-1) + 1.0/n; ΠΡΟΒΛΗΜΑ: Μεγάλες απαιτήσεις μνήμης: Μετά από κάποιο n η Java δίνει StackOverflowError Ο αντίστοιχος επαναληπτικός αλγόριθμος χρειάζεται ελάχιστη μνήμη 1
ii) Υπολογισμός αριθμών Fibonacci F n = F n-1 + F n-2, για n>2, με F 1 = 1 και F 2 = 2. public static long f(int n) if (n==1) return 1; if (n==2) return 2; return f(n-1)+f(n-2); ΠΡΟΒΛΗΜΑ: Περιττή επανάληψη υπολογισμών. Π.χ., για τον υπολογισμό του F 50 : Η f(1) καλείται πάνω από 20 δις φορές!... 2
Μεταβλητές τοπικές (ορίζονται μέσα σε μεθόδους) κλάσης (ή αντικειμένου, ή στιγμιότυπου) Μεταβλητές κλάσης: Ορίζονται μέσα στην κλάση, εκτός μεθόδων (συνήθως στην αρχή της κλάσης) Έχουν (συνήθως) ορατότητα: public ή private public: ορατές από μεθόδους της κλάσης τους και από μεθόδους άλλων κλάσεων του προγράμματος private: ορατές μόνο από μεθόδους της κλάσης τους [Σημείωση: οι τοπικές μεταβλητές δεν έχουν δήλωση ορατότητας] Ο τρόπος ορισμού τους είναι ο εξής: [ορατότητα] [static] <τύπος> <όνομα> [= τιμή]; (Ανάμεσα σε [ ] τα προαιρετικά μέρη της δήλωσης και ανάμεσα σε < > τα υποχρεωτικά). Ο «σωστός» προγραμματισμός επιβάλει την χρήση private μεταβλητών κλάσεων. Μέχρι τώρα είχαμε δει μόνο static μεταβλητές και μεθόδους. Οι static μεταβλητές και μέθοδοι στην Java είναι κάτι το εξειδικευμένο. Γενικά, χρησιμοποιούνται μεταβλητές και μέθοδοι που δεν είναι static. Αυτές οι μεταβλητές και οι μέθοδοι στην ουσία δεν «ανήκουν» στην κλάση στην οποία ορίζονται (όπως οι αντίστοιχες static), αλλά ανήκουν στα αντικείμενα (στιγμιότυπα) της κλάσης αυτής, και έχουν ένα «αντίγραφο» για κάθε αντικείμενο της κλάσης. Ας δούμε τι είναι τα αντικείμενα των κλάσεων: Αντικείμενα (στιγμιότυπα) κλάσεων: Στην Java, οι κλάσεις είναι «καλούπια» για τη δημιουργία αντικειμένων. Τα αντικείμενα είναι στην ουσία πολύπλοκες μεταβλητές τύπου κλάσης, οι οποίες συγκεκριμενοποιούν τις μεταβλητές και τις μεθόδους των κλάσεων. Π.χ., έχουμε μία κλάση Car η οποία περιέχει μεταβλητές για διάφορα χαρακτηριστικά των αυτοκινήτων, όπως το χρώμα τους (color), την μάρκα τους (brand), την ιπποδύναμή τους (power), το βάρος τους (weight), κτλ., και διάφορες μεθόδους (οι οποίες δεν θα μας απασχολήσουν αυτή τη στιγμή). 3
public class Car public String color; public String brand; public int power; public int weight;.. Η κλάση αυτή είναι ένα «καλούπι» που περιέχει τα χαρακτηριστικά ενός αυτοκινήτου (γενικά). Για να δόσουμε τιμές στα χαρακτηριστικά αυτά για κάποιο συγκεκριμένο αυτοκίνητο, πρέπει να φτιάξουμε ένα αντικείμενο της κλάσης αυτής, μέσα φυσικά από κάποια άλλη κλάση του προγράμματος. Ο τρόπος είναι ο εξής: Car car1 = new Car(); δηλαδή: <Κλάση> <αντικείμενο> = new <Κλάση>(); Ομοίως, μπορούμε να φτιάξουμε και δεύτερο αντικείμενο της κλάσης, κτλ. Car car2 = new Car(); Αφού φτιαχθούν αντικείμενα της κλάσης Car, μπορούν να δοθούν συγκεκριμένες τιμές στις μεταβλητές της κλάσης, για κάθε αντικείμενο φυσικά (αφού η κάθε μεταβλητή της κλάσης Car έχει ένα «αντίγραφο» για κάθε αντικείμενο!). Έτσι, π.χ., στην κλάση εφαρμογής του προγράμματος που περιέχει την κλάση υποστήριξης Car, θα μπορούσαν να υπάρχουν τα ακόλουθα: public class CarExample public static void main(string [] args) // Δημιουργία αντικειμένων: Car car1 = new Car(); Car car2 = new Car(); // Απόδοση τιμών σε μεταβλητές: car1.color = άσπρο ; //το χρώμα του 1 ου αυτ/του car2.color = μαύρο ; //το χρώμα του 2 ου αυτ/του car1.brand = bmw ; //η μάρκα του 1 ου αυτ/του car2.brand = fiat ; //η μάρκα του 2 ου αυτ/του car1.power = 200; //η ιπποδύναμη του 1 ου αυτ/του car2.power = 80; //η ιπποδύναμη του 2 ου αυτ/του // Output: System.out.println( Το 1 ο αυτοκίνητο είναι + car1.brand + και έχει + car1.power + ίππους. ); System.out.println( Το 2 ο αυτοκίνητο είναι + car2.brand + και έχει + car2.color + χρώμα. ); 4
Η πρόσβαση στις μεταβλητές της Car είναι εφικτή επειδή τις δηλώσαμε σαν public. Κανονικά θα πρέπει να είναι private (σύμφωνα με τις αρχές του «σωστού» προγραμματισμού). Σε αυτή την περίπτωση, η κλάση Car θα ήταν ως εξής: public class Car private String color, brand; private int power, weight; // μέθοδοι για απόδοση τιμών στις private μεταβλητές: public void setcolor(string c) color = c; public void setbrand(string b) brand = b; public void setpower(int p) power = p; public void setweight(int w) weight = w; // μέθοδοι για επιστροφή των τιμών των private μεταβλητών: public String getcolor() return color; public String getbrand() return brand; public int getpower() return power; public int getweight() return weight; 5
Τώρα λοιπόν που οι μεταβλητές της Car είναι private, η main μέθοδος της κλάσης εφαρμογής γίνεται ως εξής (με bold γράμματα είναι τα σημεία που αλλάζουν): public class CarExample public static void main(string [] args) // Δημιουργία αντικειμένων: Car car1 = new Car(); Car car2 = new Car(); // Απόδοση τιμών σε μεταβλητές: car1.setcolor( άσπρο ); //το χρώμα του 1 ου αυτ/του car2.setcolor( μαύρο ); //το χρώμα του 2 ου αυτ/του car1.setbrand( bmw ); //η μάρκα του 1 ου αυτ/του car2.setbrand( fiat ); //η μάρκα του 2 ου αυτ/του car1.setpower(200); //η ιπποδύναμη του 1 ου αυτ/του car2.setpower(80); //η ιπποδύναμη του 2 ου αυτ/του.. // Output: System.out.println( Το 1 ο αυτοκίνητο είναι + car1.getbrand() + και έχει + car1.getpower() + ίππους. ); System.out.println( Το 2 ο αυτοκίνητο είναι + car2.getbrand() + και έχει + car2.getcolor() + χρώμα. ); Άλλο παράδειγμα: α) αρχικά με private και public μεταβλητές: public class Class1 private int a=5; private int b=3; public int c=0; public void method1() c = 2*a+b; public class Class2 public static void main(string [] args) Class1 obj1 = new Class1(); Class1 obj2 = new Class1(); obj1.method1(); System.out.println(obj1.c); // -> 13 System.out.println(obj2.c); // -> 0 Το δεύτερο System.out.println() τυπώνει την τιμή 0 γιατί η method1() δεν έχει κληθεί για το αντικείμενο obj2, παρά μόνο για το obj1, άρα η τιμή της c για το obj2 δεν έχει αλλάξει από την αρχική τιμή 0. 6
β) Καλύτερη προσέγγιση: Χρήση μεθόδων προσπέλασης (accessor methods) Κάνουμε private τη μεταβλητή c Προσθέτουμε accessor method στην Class1: public int method2() return c; Στην Class2, αλλάζουν οι δύο τελευταίες εντολές (τα System.out.println) ως εξής: System.out.println(obj1.method2()); // -> 13 System.out.println(obj2.method2()); // -> 0 Ο λόγος για τον οποίο η δεύτερη προσέγγιση (με private τη μεταβλητή c και χρήση accessor method για την πρόσβασή της από την κλάση Class2) είναι η πιο σωστή, είναι ότι έτσι δεν επιτρέπεται σε κάποιον να αλλάξει/τροποποιήσει την τιμή της μεταβλητής c με όποιον τρόπο θέλει. Η τιμή της c καθορίζεται με τον τρόπο που έχει προβλέψει ο προγραμματιστής της Class1, μέσω της μεθόδου method1() (και παίρνει την τιμή 2*a+b). Ανακεφαλαιώνοντας, ενώ για την κλήση static μεθόδων είχαμε: <Κλάση>.<μέθοδος>(); για την κλήση κανονικών μεθόδων έχουμε: <αντικείμενο>.<μέθοδος>(); 7
Παράδειγμα με την κλάση Circle: Η κλάση υποστήριξης: public class Circle private double radius, area, circ; // μέθοδος για απόδοση τιμής στη μεταβλητή radius: public void setradius(double r) radius = r; // μέθοδος για τον υπολογισμό της επιφάνειας κύκλου: public void calcarea() area = Math.PI * Math.pow(radius,2); // μέθοδος για τον υπολογισμό της περιφέρειας κύκλου: public void calccirc() circ = 2*Math.PI*radius; // απαραίτητες accessor methods: public double getarea() return area; public double getcirc() return circ; 8
Και η κλάση εφαρμογής: import java.util.*; public class CirclesExample public static void main(string [] args) Scanner input = new Scanner(System.in); System.out.println( Enter radius of 1st circle: ); double r1 = input.nextdouble(); System.out.println( Enter radius of 2nd circle: ); double r2 = input.nextdouble(); Circle c1 = new Circle(); Circle c2 = new Circle(); // αποστολή τιμών των ακτίνων: c1.setradius(r1); c2.setradius(r2); // υπολογισμός επιφανειών των 2 κύκλων: c1.calcarea(); c2.calcarea(); // υπολογισμός περιφερειών των 2 κύκλων: c1.calccirc(); c2.calccirc(); // output: System.out.println( Ο πρώτος κύκλος έχει επιφάνεια + c1.getarea() + και περιφέρεια + c1.getcirc()); System.out.println( Ο δεύτερος κύκλος έχει επιφάνεια + c2.getarea() + και περιφέρεια + c2.getcirc()); 9