Εισαγωγή στην Java - κεφ. 8

...ασύγχρονα μαθήματα γλώσσας Java

Εισαγωγή στην Java - κεφ. 8

Δημοσίευσηαπό alkismavridis » 25 Ιαν 2013, 15:36

Προηγούμενο: Σύνδεση προγραμμάτων - Σχέσεις μεταξύ Αντικειμένων και Κλάσεων
Επόμενο: Τρεις χρήσιμες κλάσεις


Πίνακες - Πέρασμα ορισμάτων


Σκοπός αυτού του κεφαλαίου είναι να γνωρίσουμε τους πίνακες: ένα πολύ χρήσιμο τρόπο να ομαδοποιούμε δεδομένα του ίδιου τύπου. Θα μάθουμε επίσης πως θα επιτρέπουμε στον χρήστη να περνάει ορίσματα στα προγράμματα που φτιάχνουμε!

1. Εισαγωγή - Η έννοια του πίνακα
Η μαθηματική έννοια του πίνακα είναι ένα σύνολο από στοιχεία, στα οποία έχει σημασία η διάταξη (η σειρά τους με άλλα λόγια).
Αυτή τη διάταξη μπορούμε να τη φανταστούμε σαν μία γραμμή από στοιχεία το ένα πίσω από το άλλο (δηλαδή μονοδιάστατα), ή σαν ένα επίπεδο όπου το κάθε στοιχείο καταλαμβάνει ένα «τετραγωνάκι» (δισδιάστατα), ή σαν ένα κύβο στον οποίο το κάθε στοιχείο καταλαμβάνει ένα μικρό «κυβάκι» (τρισδιάστατα). Κατά την ίδια λογική μπορούμε να έχουμε και πίνακες με περισσότερες διαστάσεις που, αν και δεν μπορούμε να φανταστούμε με την όρασή μας, μπορούμε να καταλάβουμε ως ιδέα.

Για να αναγνωρίσουμε αυτά τα στοιχεία (να πούμε ποιο είναι ποιο) χρειαζόμαστε την «διεύθυνσή τους μέσα στον πίνακα». Τι είναι πρακτικά όμως αυτή η διεύθυνση;
Θα συμφωνήσετε ότι σε ένα μονοδιάστατο πίνακα το μόνο που θα χρειαστούμε για να προσδιορίσουμε ένα στοιχείο του, είναι ένας ακέραιος αριθμός που να δηλώνει τη σειρά του. Αν πούμε πχ το "στοιχείο αριθμός 2" θα πάμε στην αρχή του πίνακα, θα προσπεράσουμε το πρώτο στοιχείο, θα βρούμε το δεύτερο και τέλος!
Σε ένα δισδιάστατο πίνακα όμως θα χρειαστούμε δύο ακέραιους αριθμούς (όπως οι συντεταγμένες ενός χάρτη) για να βρούμε το κάθε στοιχείο. Άρα η διεύθυνση του κάθε στοιχείου θα αποτελείται από δύο ακέραιους! Με την ίδια λογική σε ένα τρισδιάστατο πίνακα χρειαζόμαστε τρεις και πάει λέγοντας.
Έτσι η διεύθυνση του στοιχείου ενός πίνακα είναι τόσοι ακέραιοι, όσες και οι διαστάσεις του πίνακα.


2. Οι πίνακες στην Java - Μονοδιάστατοι πίνακες
Ας δούμε τώρα τι είναι ο πίνακας στον προγραμματισμό, και συγκεκριμένα στην Java.
Στους γνώστες C/C++ οι πίνακες θα φανούν παιχνιδάκι, γιατί στην Java πολύ πιο απλή χρήση από ότι σε αυτές τις γλώσσες.

Ας αρχίσουμε: κατ αρχήν οι πίνακες (ακριβώς όπως είπαμε παραπάνω) είναι ένα σύνολο από στοιχεία με μία ορισμένη διάταξη. Τα στοιχεία αυτά είναι όμως ίδιου τύπου!
Δεν υπάρχει «πίνακας» γενικά και αόριστα, αλλά υπάρχει «πίνακας int», «πίνακας boolean», «πίνακας String» ή πίνακας οποιουδήποτε άλλου είδους.
Ένας πίνακας int θα περιέχει μονάχα int, και ένας πίνακας από Scanner θα περιέχει μονάχα Scanner!


2.1 Δήλωση
Πριν χρησιμοποιήσουμε ένα πίνακα, πρέπει να τον δηλώσουμε και να τον αρχικοποιήσούμε. Ας δούμε πως γίνεται η δήλωση:
Γράφουμε ως είδος, το είδος των στοιχείων του (πχ int) ακολουθούμενο από άδειες τετράγωνες αγκύλες. Κάπως έτσι δηλαδή:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int[] myarray;


ή γράφοντας κανονικά τον τύπο, και βάζοντας τις αγκύλες στο όνομα της μεταβλητής:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int myarray[];


Οι δύο παραπάνω τρόποι είναι εντελώς ισοδύναμοι. Η διαφορά των δύο συντάξεων γίνεται φανερή όταν δηλώνουμε πολλές μεταβλητές μαζί.
Αν χρησιμοποιήσουμε τις αγκύλες στον τύπο (πχ int[]), τότε όλες οι μεταβλητές που ακολουθούν θα είναι πίνακες. Έτσι δηλώνουμε μόνο πίνακες με αυτή την εντολή.
Αν όμως βάλουμε τις αγκύλες στην μεταβλητή (πχ myarray[]), τότε οι υπόλοιπες μεταβλητές δεν είναι πίνακες. Έτσι σε μία εντολή δηλώνουμε και πίνακες και απλές μεταβλητές:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int[] a, b, c; //εδώ έχουμε δηλώσει τρείς πίνακες
int a, b[], c; //ενώ εδώ έχουμε δηλώσει έναν μόνο πίνακα, και δύο απλούς int...


Κάτι σηματνικό: όταν έχουμε ένα πίνακα από short με όνομα sh, το όνομα του πίνακα είναι sh, και ο τύπος του είναι short[]. Έχει σημασία να καταλάβετε ότι ούτε το όνομά του είναι sh[], ούτε το είδος του short. Ακόμα και αν δηλώνεται ως:
Μορφοποιημένος Κώδικας: Επιλογή όλων
short sh[];

Αυτό δεν πρέπει να σας παραπλανεί! Το όνομα είναι sh και ο τύπος short[].

Επίσης όπως και με όλα τα αντικείμενα, μπορούμε να δηλώσουμε έναν πίνακα ως public, protected, private, static ή και final. Τα αποτέλεσμα θα είναι ακριβώς το ίδιο όπως το γνωρίζουμε μέχρι τώρα για τα υπόλοιπα αντικείμενα. Αν πχ ένας πίνακας είναι private, ένα άλλο πρόγραμμα δε θα μπορεί να έχει πρόσβαση ούτε στα στοιχεία του πίνακα, ούτε και στον πίνακα καθ' εαυτό.


2.2 Αρχικοποίηση
Προς το παρόν δεν έχουμε δημιουργήσει κανένα πίνακα, απλώς μεταβλητές που «αναφέρονται» σε πίνακες. Θυμηθείτε τι έχουμε πεί για τα αντικείμενα... Ο πίνακας είναι απλώς ένα αντικείμενο!!
Επόμενο βήμα είναι να δώσουμε αρχική τιμή στον πίνακά μας, να δημιουργήσουμε χώρο στη μνήμη για αυτόν. Αυτό γίνεται όπως περίπου κάναμε με τα αντικείμενα: Γράφουμε τη λέξη-κλειδί new, ακολουθούμενη από τον τύπο του πίνακα. Όμως στον τύπο, οι αγκύλες [] δε θα είναι άδειες: πρέπει να γράφουν πόσα στοιχεία έχει ο πίνακας. Κάπως έτσι δηλαδή:
Μορφοποιημένος Κώδικας: Επιλογή όλων
myarray = new int[50];

Ο πίνακας αυτός θα περιέχει 50 ακεραίους.
Οι ακέραιοι αυτοί προς το παρόν έχουν όλοι την default τιμή για ακεραίους, δηλαδή την τιμή μηδέν. Με την ίδια λογική, σε ένα πίνακα boolean οι boolean θα ήταν όλες false, και σε ένα πίνακα με αντικείμενα, θα ήταν όλα null.


Υπάρχει όμως και ένας ακόμα τρόπος αρχικοποίησης. Να γεμίσουμε κατ' ευθείαν τον πίνακα με τις τιμές που θέλουμε! Αυτό γίνεται γράφοντας το new, τον τύπο του πίνακα με άδειες τις αγκύλες [], και μετά άλλες αγκύλες {} όπου μέσα θα έχουμε όλα τα στοιχεία, χωρισμένα με κόμμα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
όνομα = new τύπος_στοιχείων[] {στοιχείο_0, στοιχείο_1, στοιχείο_2, κτλ... , };

//πχ

int[] myarray;
myarray = new int[] {5, 89, 32, 622};
//ή
String[] sarray;
sarray = new String[] {"Η", "Java", "είναι", "παιχνιδάκι", "!!!", "\n"};

//ή ακόμα και

MyObject[] ar;
MyObject ob1 = new MyObject(4);
ar = new MyObject[] {new MyObject(80), new MyObject(70), ob1};
//Σκεφτείτε αυτό το τελευταίο...


Κλείνοντας, να επισημάνουμε (όπως φαίνεται και στο προηγούμενο παράδειγμα) ότι η δήλωση και η αρχικοποίηση μπορούν να γίνουν σε μία εντολή. πχ:
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[] l = new long[80];
//ή
String[] sa = new String[] {"Οι", "πίνακες", "είναι", "χρήσιμοι"};



2.3 Χρήση
Τώρα που έχουμε δημιουργήσει τον πίνακά μας μπορούμε να χρησιμοποιήσουμε τα στοιχεία του. Αυτό είναι άλλωστε και το νόημά του!
Να τα διαβάσουμε και να τα τροποποιήσουμε.
Για να το κάνουμε αυτό γράφουμε το όνομά του ακολουθούμενο από αγκύλες που εσωκλείουν έναν ακέραιο, ο οποίος είναι η «διεύθυνση» του στοιχείου στον πίνακα. πχ:

Μορφοποιημένος Κώδικας: Επιλογή όλων
myarray[0] = 1;
//ή
System.out.print(myarray[4]); //κτλ


Το πρώτο στοιχείο κάθε πίνακα είναι το στοιχείο 0, όχι το στοιχείο 1!! Και το τελευταίο είναι το πλήθος των στοιχείων μείον ένα, δηλαδή στην περίπτωσή που δημιουργήσαμε πίνακα με 50 στοιχεία, το στοιχείο υπ'αριθμόν 49 είναι το τελευταίο, και όχι 50! Στοιχείο υπ' αριθμόν 50 δεν υπάρχει!



Να σημειώσουμε επίσης ότι μπορούμε να βάλουμε μέσα στις αγκύλες (και κατά την δήλωση και κατά την προσπέλαση στοιχείων) μία μεταβλητή ακεραίου αντί για έναν ακέραιο αριθμό. πχ τα παρακάτω είναι απόλυτα σωστά:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int i=50;
lonh[] myarray;
myarray = new long[i];
for (i=0; i<50; ++i) myarray[i] = i*i;

//ή ακόμα και
i=5;
System.out.print( myarray[i*i-12] );
//Αυτό θα υπολογίσει i*i-12=13, άρα θα τυπώσει το myarray[13]!!


Και με την ευκαιρία να ξεκαθαρίσουμε την διαφορά μεταξύ πίνακα και στοιχείου του πίνακα.
Μπορούμε να φανταστούμε τον πίνακα σαν μία πλαστική παγοθήκη. Όλοι καταλαβαίνουμε ότι άλλο το παγάκι, και άλλο η παγοθήκη! Μπορούμε σαφώς να διακρίνουμε αυτά τα δύο. Η παγοθήκη υπάρχει ακόμα και όταν δεν έχει κανένα παγάκι μέσα της! Όταν λέμε «δώσε μου ένα παγάκι», αναφερόμαστε σε ένα στοιχείο της παγοθήκης, ενώ αν πούμε «δώσε μου την παγοθήκη» εννοούμε και τα παγάκια που έχει μέσα της (αν έχει), και την παγοθήκη την ίδια.

Ακριβώς με τον ίδιο τρόπο, όταν γράφουμε στο πρόγραμμά μας κάτι σαν: myarray[0], myarray[10], myarray[1821], myarray[i] κτλ, αναφερόμαστε σε ένα στοιχείο του πίνακα myarray, με άλλα λόγια σε ένα long! Δεν μας νοιάζει καθόλου αν αυτός ο long ήρθε μέσα από ένα πίνακα, ή ήταν ένας απλός long, όπως βλέπαμε μέχρι τώρα. Απλώς κρατάμε στα χέρια μας ένα long, και μπορούμε να κάνουμε με αυτό οτιδήποτε ξέρουμε μέχρι τώρα.
Αυτό αντιστοιχεί στο «δώσε μου ένα παγάκι». Παραδείγματα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[] lng = new long[50];

//...δίνουμε τιμές στα 50 στοιχεία του lng...

if (lng[5]>80) System.out.print(">80 ");
lng[5]++;
lng[lng[3]] = 5; //Σκεφτείτε αυτή τη γραμμή: εδώ πρώτα διαβάζει το lng[3], και μετά πάει στην θέση του lng που έγραφε το lng[3]

//ή και
for (int i=0; i<lng[7]; i++) //...κάνε κάτι...


Όταν όμως γράφουμε σκέτο το όνομα του πίνακα χωρίς τετράγωνες αγκύλες, αναφερόμαστε στον πίνακα αυτό καθ' εαυτό. Αυτό αντιστοιχεί στο «δώσε μου την παγοθήκη» που λέγαμε πριν. Παράδειγμα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
byte[] bt = new byte[100], bt2; //δηλώνουμε δύο πίνακες. Αρχικοποιούμε μόνο τον ένα...

// ...δίνουμε τιμές στα 100 στοιχεία του bt...

bt2 = bt; //Κάνουμε τους δύο πίνακες ίσους.


Κατά τη χρήση των πινάκων, δε θα δείτε ποτέ άδειες αγκύλες []. Αυτές εμφανίζονται μόνο κατά τη δήλωση, και ίσως μετά από την λέξη-κλειδί new.


Κλείνοντας αυτή την παράγραφο να σημειώσουμε ότι η ισότητα πινάκων στο προηγούμενο παράδειγμα λειτουργεί ακριβώς όπως η ισότητα αντικειμένων! Με άλλα λόγια η τελευταία εντολή του παραδείγματος δεν δημιουργεί ένα δεύτερο πίνακα, αλλά ταυτίζει δύο μεταβλητές.
Αν αυτά που λέω σας φαίνονται Κινέζικα, θα σας συμβούλευα να κάνετε μία επανάληψη την ενότητα 5 του κεφαλαίου «Κλάσεις, Αντικείμενα και Μέθοδοι».


2.3 Η μεταβλητή length
Η Java μας έχει δώσει ένα πολύ ωραίο εργαλείο για να μπορούμε να διαβάζουμε ανά πάσα στιγμή το μήκος ενός πίνακα!
Αυτή είναι η μεταβλητή length. Πρόκειται για μία final int μεταβλητή που κάθε πίνακας έχει από τη στιγμή που θα αρχικοποιηθεί.
Είπαμε πως ο πίνακας είναι ένα αντικείμενο όπως όλα τα άλλα (οκ, σχεδόν όπως όλα τα άλλα). Άρα για να διαβάσουμε την μεταβλητή length γράφουμε απλώς:
Μορφοποιημένος Κώδικας: Επιλογή όλων
myarray.length;


Πχ:
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[] numbers = new long[248];
//... Μετά από πολλέεεεες γραμμές κώδικα...

int myarraylength = numbers.length;
//ή

System.out.print("Ο πίνακας numbers έχει "+numbers.length+" long...");

//ή ακόμα και
int otherarray[] = new int[numbers.length+50];


Έχετε κατά νου πως όποιος και αν είναι ο τύπος του πίνακα, η length είναι πάντα ένας int.
Αν ο ar είναι int[], το ar.length είναι int
Αν ο la είναι long[], το la.length είναι πάλι int
Αν ο whatever είναι Scanner[], το whatever.length είναι επίσης int!!


Αν ar είναι ένας πίνακας, το στοιχείο υπ' αριθμόν ar.length ΔΕΝ υπάρχει!!


Με άλλα λόγια το τελευταίο στοιχείο ενός πίνακα ar είναι πάντα το ar[ ar.length-1 ].
Γι αυτό και συνηθίζεται όταν γράφουμε μία δομή for να λέμε κάτι σαν:
Μορφοποιημένος Κώδικας: Επιλογή όλων
for (int i=0; i<ar.length; ++i) ar[i] = ...κάτι... ;
//Παρατηρείστε ότι γράφουμε "<", όχι "<=", γιατί όταν το i φτάσει στο "=", θα παραβιάσει τα όρια του πίνακα


Το ότι η μεταβλητή length είναι final, μας λέει κάτι πολύ σημαντικό για τους πίνακες. Ότι από τη στιγμή που θα δημιουργήσουμε ένα πίνακα, δε μπορούμε να τον «μεγαλώσουμε» ή να τον «μικρύνουμε».
Αν δηλαδή φτιάξουμε ένα πίνακα με 80 στοιχεία, και στην πορεία πούμε «ωχ! τελικά ήθελα να τον κάνω 100!», η μόνη λύση για να σωθείτε είναι να φτιάξετε ένα καινούριο πίνακα με 100 στοιχεία, και να αντιγράψετε τα πρώτα 80 στον καινούριο, κάπως έτσι:
Μορφοποιημένος Κώδικας: Επιλογή όλων
short sh = new short[80];

//Πολέεεεες γραμμές κώδικα...
//ώχ! Τελικά ήθελα 100

short[] temp = new temp[100];
for (int i=0; i<sh.length; ++i) temp[i]=sh[i];
sh = temp; //βάλε το τη μεταβλητή sh να δείχνει στον καινούριο πίνακα..


Παρ' όλα αυτά καλό είναι να προσπαθείτε να προβλέπετε από την αρχή πόσα στοιχεία πρέπει να είναι ο πίνακάς σας, γιατί διαδικασίες όπως η παραπάνω τρώνε κάποιο χρόνο την ώρα της εκτέλεσης..

Ένα λάθος που κάνουν πολύ συχνά οι νέοι προγραμματιστές είναι να καλούν την μεταβλητή length, πριν δημιουργηθεί ο πίνακας, πριν δηλαδή κληθεί η λέξη-κλειδί new. Κάτι τέτοιο θα κρασάρει το πρόγραμμα, αφού η length είναι απλώς ένα πεδίο του αντικειμένου «πίνακας». Αν δεν έχει δημιουργηθεί ακόμα ο πίνακας, δεν υπάρχει η length!


Για να τσεκάρουμε αν ένας πίνακας δημιουργήθηκε απλώς γράφουμε:
Μορφοποιημένος Κώδικας: Επιλογή όλων
if (myarray != null) //...



2.4 Παραβίαση ορίων
Και αν πάω να διαβάσω ή να τροποποιήσω να στοιχείο που δεν υπάρχει τι θα γίνει;
Αν πχ έχουμε:

Μορφοποιημένος Κώδικας: Επιλογή όλων
//...
int[] myarray = new int[80];
//Δίνουμε τιμές στα 80 στοιχεία...

int i;
Scanner sc = new Scanner(System.in);
i = sc.nextInt();
System.out.print( myarray[i]);


Δηλαδή έχουμε έναν πίανκα με 80 στοιχεία, και ζητάμε από το χρήστη να μας δώσει ένα ακέραιο, και του τυπώνουμε την αντίστοιχη θέση του πίνακα.
Αν τώρα ο χρήστης δώσει 81 τι θα γίνει; Ή, ακόμα χειρότερα, αν δώσει -4;
Η απάντηση είναι ότι το πρόγραμμα απλώς θα "τα τινάξει". Θα τερματίσει για να το πω πιο ήπια :-)
Υπάρχει βέβαια τρόπος να χειριστούμε μία τέτοια κατάσταση, αλλά αυτά θα τα δούμε σε επόμενα κεφάλαια...


Κλείνοντας αυτή την παράγραφο, θα δούμε ένα (ίσως χρήσιμο!) παράδειγμα με μονοδιάστατους πίνακες:
Μορφοποιημένος Κώδικας: Επιλογή όλων
public class Array1D {

public static void main(String[] args) {

int[] d;
int len,i;
java.util.Scanner sc = new java.util.Scanner(System.in);


System.out.print("Θα υπολογίσω τον μέσο όρο ενός δείγματος ακεραίων!\nΠόσο μεγάλο είναι το δείγμα σου; ");
len = sc.nextInt();
d = new int[len];

System.out.print("Εντάξει λοιπόν! Δώσε μου "+len+" ακέραιους.\n");
for(i=0; i<len; ++i) d[i] = sc.nextInt();

//Υπολογίζουμε το άθροισμα
int sum=0;
for (i=0; i<len; ++i) sum = sum + d[i];

System.out.print("Μέσος όρος: "+ (1.*sum/len) +"\n");

}//main
}//class



3. Πολυδιάστατοι πίνακες
Αν έχετε καταλάβει καλά πως λειτουργούν οι μονοδιάστατοι πίνακες, τότε οι πολυδιάστατοι θα σας φανούν σαν προφανής γενίκευση. Και αυτό γιατί όπως θα δούμε, η μνήμη του υπολογιστή μας δεν καταλαβαίνει από «πολυδιάστατα» πράγματα!!

Ας πάρουμε σαν παράδειγμα ένα δισδιάστατο πίνακα από int.
Τί περιέχει ένας τέτοιος πίνακας; Με τι γεμίζει;
Η προφανής απάντηση ότι «περιέχει int» δεν είναι απόλυτα σωστή. Στην πραγματικότητα περιέχει πολλούς πίνακες int[]. Είναι ένας πίνακας πινάκων!
Ο τύπος ενός τέτοιου αντικειμένου είναι int[][].

Ένας τρισδιάστατος πίνακας int[][][] είναι ένας πίνακας από int[][] και πάει λέγοντας...

3.1 Δήλωση
Η δήλωση τέτοιων αντικειμένων είναι πολύ παρόμοια με τους μονοδιάστατους πίνακες:
Μορφοποιημένος Κώδικας: Επιλογή όλων
String[][] message;
int[][] myarray1, myaray2;
//ή και
boolean conditions[][];



3.2 Αρχικοποίηση
Εδώ όμως έχουμε περισσότερες ελευθερίες, άρα και περισσότερες περιπτώσεις :-(

Ας αρχίσουμε από τον παραδοσιακό τρόπο:
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[][] ln;
ln = new long[30][4];

//ή και
byte ba[][][];
ba = new byte[8][8][10];
Έτσι φτιάχνουμε έναν πίνακα 30x4, και έναν 8x8x10 αντίστοιχα.

Ομοίως θα λειτουργούσε και το:
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[][] ln;
int i;
//δίνουμε τιμή στο i...
ln = new long[i*4-2][i-1];
//και ομοίως για περισσότερες διαστάσεις
Δηλαδή δε χρειάζεται ο ακέραιος μέσα σε κάθε αγκύλη να εμφανίζεται άμεσα...


Ας σκεφτούμε λίγο τι σημαίνει από τη σκοπιά του υπολογιστή η εντολή
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[][] ln = new long[10][7];

Σημαίνει ότι αρχικά πρέπει να δημιουργήσει ένα πίνακα με 10 στοιχεία.
Έπειτα για κάθε ένα από αυτά τα 10 δημιουργεί ένα πίνακα με 7 long.
Το τελικό αποτέλεσμα είναι ένας πίνακας πινάκων long.

Όμως μπορούμε να διακόψουμε τη διαδικασία στο πρώτο βήμα! Δηλαδή να του πούμε: φτιάξε ένα πίνακα που θα έχει χώρο για 10 στοιχεία (τα οποία θα είναι πίνακες), αλλά προς το παρόν άφησέ τα κενά.

Αυτό γίνεται πολύ απλά έτσι:
Μορφοποιημένος Κώδικας: Επιλογή όλων
long[][] ln;
ln = new long[10][];


Τώρα κάθε στοιχείο του πίνακα ln είναι ένας πίνακας long[], αλλά προς το παρόν... είναι null.
Μπορούμε βέβαια να τα αρχικοποιήσουμε ένα-ένα όπως ξέρουμε πολύ καλά. Πχ.
Μορφοποιημένος Κώδικας: Επιλογή όλων
ln[0] = new long[7];
σκεφτείτε το λίγο... Όταν γράφουμε ln[0], κρατάμε στα χέρια μας ένα πίνακα long[], και κάνουμε με αυτόν ό,τι ξέραμε μέχρι τώρα!

Ας δούμε και μερικά παραδείγματα αρχικοποίησης με περισσότερες διαστάσεις:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int[][][] num = new int[5][][];
boolean bvalues = new boolean[2][5][];

//και αργότερα για αρχικοποίηση:
num[0] = new int[3][3]; //εδώ αρχικοποιούμε εντελώς
num[1] = new int[8][]; //ενώ εδώ μέχρι το «στοιχείο του στοιχείου» μόνο..

bvalues[0][2] = new boolean[8];


Και εδώ να ξεκαθαρίσουμε ότι στην Java, όπως και σε όλες τις γλώσσες τύπου-C, όταν έχουμε πίνακες πινάκων, δε χρειάζεται οι τελευταίοι να έχουν όλοι το ίδιο μήκος.
Για παράδειγμα ο κώδικας:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int[][] numbers = new int[2][];

numbers[0] = new int[10]; //αρχικοποιούμε το πρώτο στοιχείο του numbers...
numbers[1] = new int[5]; //αρχικοποιούμε και το δεύτερο.
δημιουργεί ένα πίνακα int[][] με δύο στοιχεία, τα οποία όμως έχουν άνισα μήκη! Το πρώτο χωράει 10 int, το δεύτερο 5.
Και όμως αυτό είναι απόλυτα σωστό από στη σκοπιά του προγραμματισμού! Άρα:

Τα στοιχεία ένός πολυδιάστατου πίνακα Δεν έχουν κατ' ανάγκη ίδιο μέγεθος.



Off topic:
Αυτό νομίζω πως αντικρούει τον μαθηματικό ορισμό του πίνακα, και γι αυτό αυτοί που ξέρουν καλά μαθηματικά ίσως θεωρήσουν ότι κακώς οι προγραμματιστές χρησιμοποιούμε τη λέξη «πίνακας». Ίσως και να έχουν δίκιο, αλλά ας μήν κολλήσουμε εκεί :-)

Τέλος έχουμε και την επιλογή, όπως και στους μονοδιάστατους πίνακες, να γεμίσουμε απ'ευθείας τον πίνακά μας με τιμές. Αυτό γίνεται με την πολλαπλή χρήση αγκυλών {}
Μορφοποιημένος Κώδικας: Επιλογή όλων
short[][] sh;
sh = new short[][] {
{1,2,4,7,10},
{45, 30,64}
};
//το ότι το έγραψα σε χωριστές γραμμές δεν παίζει κανένα ρόλο.
//είναι μονάχα για να κάνω πιο ευανάγνωστο το κείμενο.
//Παρατηρείστε επίσης ότι κόμμα χρειάζεται και ανάμεσα από τους πίνακες.

//τα παρακάτω είναι επίσης σωστά:
String[][] bigstr;
String[] smallstr = new String[] {"Αυτός", "ο", "πίνακας", "θα", "μπει", "μέσα", "στον", "άλλο", "πίνακα", "ως", "στοιχείο", "!!!" };

big= new String[][] {
{"Άλλα", "στοιχεία", "του", "bigstr"},
{"κτλ", "κτλ", "κτλ"},
smallstr,
{"καταλάβατε", "την", "προηγούμεμη", "γραμμή", ";;;"},
{"σημαίνει", "ότι", "το", "bigstr[2]", "είναι", "το", "smallstr"}
};

σημειώστε επίσης ότι το bigstr[2] και το smallstr είναι το ίδιο αντικείμενο, απλά με δύο ονόματα. Ό,τι αλλαγή κάνετε στο ένα, θα γίνει και στο «άλλο».


3.3 Χρήση
Η χρήση των πολυδιάστατων πινάκων θα σας φανεί παιχνιδάκι, αρκεί να καταλάβετε κάθε φορά τι κρατάτε στα χέρια σας.
Για παράδειγμα αν έχουμε ένα πίνακα long[][][] με όνομα ln, τότε:

Όταν εμφανίζεται το όνομά του χωρίς αγκύλες, αυτό συμβολίζει long[][][]
Όταν εμφανίζεται με ένα ζευγάρι αγκυλών (και ένα ακέραιο μάσα τους), αυτό συμβολίζει ένα long[][].
Όταν εμφανίζεται με δύο ζευγάρια αγκύλων (και άρα δύο ακεραίους μέσα), αυτό συμβολίζει ένα long[].
Τέλος, όταν εμφανίζεται και με τις τρεις του αγκύλες, αυτό συμβολίζει έναν αριθμό long.

Από τη στιγμή που κρατάμε ένα από αυτά στα χέρια μας, κάνουμε οτιδήποτε έχουμε μάθει μέχρι τώρα με αυτό.
Ας δούμε μερικά παραδείγματα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
short[][][] mainsh = new short[2][][];
short[][] sh2;
short[] sh1;

sh1 = new short[] {1,2,3,4,5};
sh2 = new short[][] { {1,2}, {2,4}, {3,9} };

mainsh[0] = sh2;
//σωστό, γιατί και το αριστερό και το δεξί μέλος είναι short[][], άρα μπορούμε να τα εξισώσουμε!

short ashort = 5 + sh2[1][1];
//σωστό, γιατί το sh[1][1] είναι short, άρα μπορούμε να κάνουμε την πρόσθεση.

short othershort = 6 - sh1;
//Λάθος! Το sh1 είναι πίνακας! Ο τελεστής "-" δεν εφαρμόζεται σε short[].

short sharray[][] = mainsh[1];
//Σωστό! short[][] το ένα, short[][] και το άλλο!

System.out.print(sharray[0][0]);
//Λάθος! Το sharray είναι ίσο με το mainsh[1], το οποίο όμως Δεν έχει αρχικοποιηθεί ακόμα!
//Άρα ο sharray είναι ακόμη null, άρα δεν υπάρχει sharray[0][0]



Και ένα χρήσιμο παράδειγμα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
//αρχείο Powers.java
public class Powers {
public static void main(String args[]) {
int[][] powers = new int[4][10];
int i,j;

for (i=0; i<10; ++i) powers[0][i] = i+1; //δίνουμε τιμές στον πρώτο πίνακα

for (j=1; j<4; ++j) // και στους υπόλοιπους
for (i=0; i<10; ++i) powers[j][i] = powers[j-1][i]*(i+1);

//τυπώνουμε!
System.out.print("Οι αριθμοί:\n");
for (j=0; j<10; ++j) System.out.print(powers[0][j] + " ");

System.out.print("\nΤα τετράγωνα:\n");
for (j=0; j<10; ++j) System.out.print(powers[1][j]+ " ");

System.out.print("\nΟι κύβοι:\n");
for (j=0; j<10; ++j) System.out.print(powers[2][j]+ " ");

System.out.print("\nΣτην τετάρτη:\n");
for (j=0; j<10; ++j) System.out.print(powers[3][j]+ " ");
System.out.print("\n");
}//main

}//class



3.4 Η μεταβλητή length σε πολυδιάστατους πίνακες
Η γενική αρχή είναι: «όπου πίνακας και length»!
Ας δούμε πως λειτουργεί. Ας πούμε ότι έχουμε ένα πίνακα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
Object array[][] = new Object[4][30];


Ο array είναι πίνακας. Άρα έχει ένα length. Αυτό είναι το 4, αφού αν το καλοσκεφτείτε, ο array είναι ένας πίνακας που έχει ως στοιχεία του 4 πίνακες. Θα είναι λοιπόν:
array.length = 4;

Όμως και ο array[0] είναι κι αυτός πίνακας! Άρα πρέπει να έχει και εκείνος το length του. Αυτό όπως καταλαβαίνετε είναι το 30. Άρα:
array[0].length = 30;

Σημαντικό να καταλάβετε ότι τόσο η έκφραση array.length, όσο και η array[0].length είναι σωστές, και η κάθε μία μας λέει το μέγεθος του αντίστοιχου πίνακα.


4. Πίνακες ως όρισμα ή τύπος επιστροφής
Οι πίνακες μπορούν να γίνουν κάλλιστα όρισμα, ή και τύπος επιστροφής μίας μεθόδου!

Ας δούμε μερικά παράδειγμα δήλωσης μεθόδου που παίρνει ως όρισμα πίνακα, ή που επιστρέφει πίνακα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
//πίνακας ως όρισμα...
public void printSum(int[][] array) {/*...*/}

private static boolean sumEqualsTo(long sum, long[] la) {/*...*/}

//και ως επιστροφή...
public int[] propaidia(int i) {
int[] ret;
//...
return ret;
}//propaidia

static String[] info() {
return new String[] {"Αυτό", "είναι", "ένα", "μήνυμα", "...", "\n"};
}


Ας δούμε τώρα παραδείγματα για το πως θα καλούσαμε τις παραπάνω μεθόδους:
Μορφοποιημένος Κώδικας: Επιλογή όλων
//πίνακας ως όρισμα...
int[][] intar = new int[][] {
{1,3,5,7},
{2,4,6,8}
};
printSum(intar);

long[] longar = new long[] {1,2,3,4,5};
if (sumEqualsTo(15, longar)) //...

//επιστροφή πίνακα
int pr[] = propedia(8);

String[] str = info();
//...


Όπως βλέπετε οι πίνακες δεν έχουν καμία ιδιαιτερότητα σε σχέση με τα άλλα αντικείμενα!


5. Η κλάση Arrays
Η Java μας έχει δώσει μία χρήσιμη κλάση με static μεθόδους, οι οποίες κάνουν χρήσιμες διεργασίες με πίνακες.
Αυτή είναι η κλάση Arrays του πακέτου java.util, και αναλυτική περιγραφή της θα βρείτε [url=...]εδώ[/url].
Σας συνιστώ ανεπιφύλακτα να ρίξετε μία ματιά, μπορεί να βρείτε εργαλεία που θα κάνουν τη ζωή σας πολύ ευκολότερη. Δώστε βάση στις μεθόδους copyOf, και sort.



6. Πέρασμα ορισμάτων κατά την εκτέλεση
Και ήρθε επί τέλους η ώρα να μιλήσουμε για αυτό το περίφημο όρισμα της main με τύπο String[]...

Ύστερα από μία ανάγνωση αυτού του κεφαλαίου, το λιγώτερο που περιμένω από εσάς είναι να καταλαβαίνετε τι σημαίνουν αυτές οι μυστήριες αγκύλες μετά το String!! :-)

Ο πίνακας αυτός γεμίζει προφανώς με String. Που τα βρίσκει όμως;
Λοιπόν για να τρέξετε το πρόγραμμά σας πατάτε την εντολή:
Μορφοποιημένος Κώδικας: Επιλογή όλων
java SomeProgram
//ή για το gcj
./SomeProgram
σωστά;

Αν το κάνετε αυτό θα διαπιστώσετε ότι ο πίνακας αυτός θα έχει μήκος μηδέν. (Δοκιμάστε να τυπώσετε στην οθόνη το arg.length κάποιας main)

Αν όμως μετά το όνομα του προγράμματος γράψετε κάτι άλλο, πχ:
Μορφοποιημένος Κώδικας: Επιλογή όλων
java MyProgram 123 test
//ή
./MyProgram 123 test

Τότε ο πίνακας arg (ή όπως αλλιώς τον ονομάσατε) θα γεμίσει με αυτές τις λέξεις. Αυτές θα χωρίζονται με κενά διαστήματα.

Το να περνάμε ορίσματα με αυτό τον τρόπο είναι πολύ χρήσιμο αν θέλουμε η «δουλειά» να γίνεται σε μία γραμμή (αυτή της εκτέλεσης του προγράμματος) και όχι σε δύο (μία της εκτέλεσης και άλλη μία ενός Scanner που θα διαβάζει κάποιο String).

Για τους γνώστες της C πρέπει να πούμε ότι εδώ το όνομα του προγράμματος δεν πιάνεται ως όρισμα, άλλωστε για τα bytecode, τα οποία είναι και η ουσία της Java, το όνομα του προγράμματος ταυτίζεται με το όνομα της κλάσης.



Επίσης αν θέλουμε να δούμε πόσα ορίσματα έδωσε ο χρήστης, χρησιμοποιούμε το arg.length. Πχ για να διασφαλίσουμε ότι ο χρήστης έδωσε έστω ένα όρισμα γράφουμε κάτι σαν:
Μορφοποιημένος Κώδικας: Επιλογή όλων
if (arg.length!=0) //...


Ας φτιάξουμε λοιπόν ένα προγραμματάκι που χρησιμοποιεί τον πίνακα αυτόν:

Μορφοποιημένος Κώδικας: Επιλογή όλων
//αρχείο Capital.java
public class Capital {

private static final String[][] data = new String[][] {
{"Ελλάδα", "Αθήνα"},
{"Αγγλία", "Λονδίνο"},
{"Γαλλία", "Παρίσι"},
{"Πολωνία", "Βαρσοβία"},
{"Σερβία", "Βελιγράδι"},
{"Ισραήλ", "Ιερουσαλήμ"},
{"Αίγυπτος", "Κάιρο"},
{"Τουρκία", "Άνκαρα"},
{"Ιαπωνία", "Τόκυο"},
{"Κίνα", "Πεκίνο"}
};

static String mes;

public static void main(String[] arg) {

if (arg.length == 0) { //αν δεν έδωσε όρισμα...
System.out.print("Παρακαλώ εκτελέστε με κάποιο όρισμα.\n");
return;
}//if

for (int i=0; i<arg.length; ++i) //για κάθε ένα από τα ορίσματα...
printCapitalOf(arg[i]);
}//main

private static void printCapitalOf(String s) {
for (int j=0; j<data.length; ++j)
if ( s.equals(data[j][0]) ) { //σημαίνει ότι βρήκε την χώρα «s» στον πίνακα data
System.out.print(data[j][0]+" --> "+data[j][1]+ "\n");
return;
}//if

System.out.print("Δεν ξέρω την πρωτεύουσα της χώρας "+s+"\n");
}//printCapitalOf

}//class

Δοκιμάστε να τρέξετε το πρόγραμμα με διάφορα ορίσματα (αν θέλετε να δουλέψει βάλτε χώρες από τον πίνακα data).


7. Αόριστο πλήθος ορισμάτων
Μία ωραία διευκόλυνση της Java είναι η δυνατότητα να αφήσουμε «ανοιχτό» το πλήθος των ορισμάτων όταν δηλώνουμε μία μεθοδο. Αυτό γίνεται με τη χρήση αποσιωποιητικών μετά το είδος του ορίσματος. Για παράδειγμα ορίζουμε τη μέθοδο:
Μορφοποιημένος Κώδικας: Επιλογή όλων
public int sumOf(int... numbers) { /*σώμα μεθόδου*/ }

Αυτό σημαίνει ότι η μέθοδος sumOf παίρνει ως όρισμα ένα αυθαίρετο πλήθος από int, ακόμα και κανένα! Δηλαδή έχουμε δικαίωμα να καλέσουμε την sumOf ως εξής:
Μορφοποιημένος Κώδικας: Επιλογή όλων
sumOf(6,8,10);
sumOf(4);
sumOf(); //κτλ

Μία τέτοια ενέργεια δημιουργεί ένα πίνακα από int (ή όποιο άλλο τύπο ορίσματος βάλουμε), και να καλεί τη μέθοδο με όρισμα τον πίνακα. Στο σώμα της μεθόδου sumOf λοιπόν, το όρισμα numbers αντιπροσωπεύει πίνακα, είναι ένα int[]. Για παράδειγμα η μέθοδος αυτή θα μπορούσε να έχει την εξής μορφή:
Μορφοποιημένος Κώδικας: Επιλογή όλων
public int sumOf(int... numbers) {
int ret=0;
for (int k=numbers.length-1; k>=0; --k) ret += numbers[k]; //μεταχειριζόμαστε το numbers ως πίνακα!
return ret;
} //sumOf

Στην πραγματικότητα θα μπορούσαμε να καλέσουμε τη μέθοδο αυτή με όρισμα ένα πραγματικό πίνακα, δηλαδή τα δύο παρακάτω είναι ταυτόσημα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
sumOf(6,7,8);
sumOf( new int[] {6,7,8} );

Υπάρχει όμως ένας περιορισμός, την «συντομογραφία» αυτή μπορούμε να τη χρησιμοποιήσουμε μόνο στο τελευταίο όρισμα μιας μεθόδου. Δηλαδή έτσι:
  • public myMethod(int i, long... l);
  • public myMethod(int i, boolean bb, float... f);
Και όχι έτσι:
  • public myMethod(int... i, long l);
  • public myMethod(int i, boolean... bb, float f);

Προηγούμενο: Σύνδεση προγραμμάτων - Σχέσεις μεταξύ Αντικειμένων και Κλάσεων
Επόμενο: Τρεις χρήσιμες κλάσεις

Creative Commons License
Η εργασία υπάγεται στην άδεια Creative Commons Αναφορά-Παρόμοια διανομή 3.0 Ελλάδα
Τελευταία επεξεργασία από alkismavridis και 22 Απρ 2014, 21:24, έχει επεξεργασθεί 7 φορά/ες συνολικά
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 8

Δημοσίευσηαπό alkismavridis » 25 Ιαν 2013, 15:42

Συγγνώμη για την αργοπορία...
Ελπίζω να τελειώσω σύντομα αυτό τον οδηγό!! :)
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 8

Δημοσίευσηαπό konnn » 25 Ιαν 2013, 15:43

alkismavridis έγραψε:Συγγνώμη για την αργοπορία...
Ελπίζω να τελειώσω σύντομα αυτό τον οδηγό!! :)


:clap: :clap:
1 Linux: Μέτριος ┃ Προγραμματισμός: Μέτριος ┃ Αγγλικά: Προχωρημένος
2 Desktop : Ubuntu 16.04 64bit
a Intel Core i3 CPU 530 2.93GHz ‖ RAM 3824 MiB ‖ Intel DH55HC -
b nVidia Device [10de:1040] (rev a1)
c eth0: Intel 82578DC Gigabit Network Connection
3 Notebook : Ubuntu 16.04 64 bit
a Intel Core i3-2365M CPU @ 1.40GHz ‖ RAM 3854 MiB ‖ LENOVO 20197
b Intel 2nd Generation Core Processor Family Integrated Graphics Controller
c 5 wlan0: Intel Centrino Wireless-N 2230 ⋮ eth0: Realtek RTL8101E/RTL8102E

Αυτόματη υπογραφή.
Άβαταρ μέλους
konnn
Συντονιστής
Συντονιστής
 
Δημοσιεύσεις: 3568
Εγγραφή: 12 Ιούλ 2010, 17:54
Τοποθεσία: Καλαμάτα
Launchpad: konnn
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 8

Δημοσίευσηαπό alkismavridis » 22 Απρ 2014, 21:25

Προσέθεσα την παράγραφο 7, που αναφέρεται στο αυθαίρετο πλήθος ορισμάτων. :D
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση


  • ΣΧΕΤΙΚΑ ΘΕΜΑΤΑ
    ΑΠΑΝΤΗΣΕΙΣ
    ΠΡΟΒΟΛΕΣ
    ΣΥΓΓΡΑΦΕΑΣ

Επιστροφή στο Μαθήματα Java

cron