PDA

View Full Version : [JAVA] perchè indice fuori dai limiti?


Nukles
10-01-2005, 11:56
Non riesco a capire come mai il seguente programma mi dà l'errore di INDEX OUT OF BOUNDS... è tutta la mattina che mi scervello, magari qualcuno più esperto mi sa aiutare... GRAZIE!!

-------------------------------------------------------

/* Applicazione che ricevendo come parametro un array a non nullo di interi
ordinato in modo non decrescente e un valore intero v, crea e restituisce un nuovo array
di interi che contiene il valore v e tutti gli elementi di a, ed è ordinato in modo
non decrescente*/

class Inserimento {
public static int[] inserisci(int[] a, int v) {
// pre: a[i] >= a[i-1]; a != null
int i; //contatore
int k; //posizione in cui inserire il nuovo elemento
int[] b; //array di uscita

/*questa variabile è un controllo di cancellazione, che serve ad informare l'istruzione ripetitiva
sulla posizione di inserimento degli elementi di b*/
boolean canc;

b = new int[a.length + 1];
canc = false; //inizialmente non si è cancellato alcun elemento

k = 0;

/*istruzione di inserimento elementi nel nuovo array*/
for (i=0; a[i] <= v; i++) {
b[i] = a[i];
k++;
}

b[k] = v;

for (i = k; i<a.length; i++)
b[i+1] = a[i];

return b;

}

private static void visualizza(int[] a) {

/* metodo che visualizza il nuovo array */
int i; //indice di scansione di a

for (i=0; i<a.length; i++)
System.out.print(a[i] + " ");
}

public static void main(String args[]) {
int i; //contatore

/*test di Inserimento*/
System.out.println("Test per int[] inserimento(int[] a, int v)");
/*nuovo elemento in prima posizione*/
visualizza( inserisci(new int[] {7, 8, 22}, 1) ); // (1, 7, 8, 22)
/*nuovo elemento in ultima posizione*/
visualizza( inserisci(new int[] {7, 8, 10}, 22) ); // (7, 8, 10, 22)
/*nuovo elemento in posizione intermetdia*/
visualizza( inserisci(new int[] {3, 4, 8, 10}, 5) ); // (3, 4, 5, 8, 10)
/*un solo elemento nell'array, numero da inserire maggiore*/
visualizza( inserisci(new int[] {8}, 10) ); // (8, 10)
/*un solo elemento nell'array, numero da inserire minore*/
visualizza( inserisci(new int[] {8}, 3) ); // (3, 8)

}

}

end.is.forever
10-01-2005, 12:18
for (i=0; a[i] <= v; i++) {
b[i] = a[i];
k++;
}

Quando accedi ad un array attraverso l'indicizzatore devi sempre assicurarti che l'argomento (i in questo caso) sia minore della lunghezza di quell'array.
In questo caso non l'hai fatto, dato che nel for i cresce potenzialmente all'infinito se non trova nessun elemento di a che sia maggiore di v.

Per cui quando tu invochi
visualizza( inserisci(new int[] {7, 8, 10}, 22) ); // (7, 8, 10, 22)
la i supera a.length facendo lanciare l'eccezione.

A prima vista mi sembra che basti sostituire con:

for (i=0; a[i] <= v && i < a.length; i++) {
b[i] = a[i];
k++;
}
if (i == a.length)
k++;

Nukles
10-01-2005, 21:23
Originariamente inviato da end.is.forever

for (i=0; a[i] <= v; i++) {
b[i] = a[i];
k++;
}

Quando accedi ad un array attraverso l'indicizzatore devi sempre assicurarti che l'argomento (i in questo caso) sia minore della lunghezza di quell'array.
In questo caso non l'hai fatto, dato che nel for i cresce potenzialmente all'infinito se non trova nessun elemento di a che sia maggiore di v.

Per cui quando tu invochi
visualizza( inserisci(new int[] {7, 8, 10}, 22) ); // (7, 8, 10, 22)
la i supera a.length facendo lanciare l'eccezione.

A prima vista mi sembra che basti sostituire con:

for (i=0; a[i] <= v && i < a.length; i++) {
b[i] = a[i];
k++;
}
if (i == a.length)
k++;


non ci avevo pensato... grazie mille end! :mano:

Nukles
11-01-2005, 09:22
Ho provato questa piccola variante, ma purtroppo ancora indexoutofbounds...


/* Applicazione che ricevendo come parametro un array a non nullo di interi
ordinato in modo non decrescente e un valore intero v, crea e restituisce un nuovo array
di interi che contiene il valore v e tutti gli elementi di a, ed è ordinato in modo
non decrescente*/

class Inserimento {
public static int[] inserisci(int[] a, int v) {
// pre: a[i] >= a[i-1]; a != null
int i; //contatore
int k; //posizione in cui inserire il nuovo elemento
int[] b; //array di uscita

/*questa variabile è un controllo di cancellazione, che serve ad informare l'istruzione ripetitiva
sulla posizione di inserimento degli elementi di b*/
boolean canc;

b = new int[a.length + 1];
canc = false; //inizialmente non si è cancellato alcun elemento

k = 0;

/*istruzione di inserimento elementi nel nuovo array*/
for (i=0; a[i] <= v && i<a.length; i++) {
b[i] = a[i];
if (a[i] <= v) k = i;
}

b[k] = v;

for (i = k; i<a.length; i++)
b[i+1] = a[i];

return b;

}

private static void visualizza(int[] a) {

/* metodo che visualizza il nuovo array */
int i; //indice di scansione di a

for (i=0; i<a.length; i++)
System.out.print(a[i] + " ");
}

public static void main(String args[]) {
int i; //contatore

/*test di Inserimento*/
System.out.println("Test per int[] inserimento(int[] a, int v)");
/*nuovo elemento in prima posizione*/
visualizza( inserisci(new int[] {7, 8, 22}, 1) ); // (1, 7, 8, 22)
/*nuovo elemento in ultima posizione*/
visualizza( inserisci(new int[] {7, 8, 10}, 22) ); // (7, 8, 10, 22)
/*nuovo elemento in posizione intermetdia*/
visualizza( inserisci(new int[] {3, 4, 8, 10}, 5) ); // (3, 4, 5, 8, 10)
/*un solo elemento nell'array, numero da inserire maggiore*/
visualizza( inserisci(new int[] {8}, 10) ); // (8, 10)
/*un solo elemento nell'array, numero da inserire minore*/
visualizza( inserisci(new int[] {8}, 3) ); // (3, 8)

}

}


eppure come logica mi sembra corretta...

end.is.forever
11-01-2005, 12:25
Scusa mi sono sbagliato a scrivere sostituisci con:

for (i=0; i < a.length && a[i] <= v; i++) {
b[i] = a[i];
k++;
}
if (i == a.length)
k++;

Cioe nella condizione nell'and prima i < a.length e poi a[i] <= v.
Probabilmente il motivo per cui ti lancia ancora quell'eccezione è che quella modifica non cambiava nulla, dato che ancora per fare il check della condizione doveva accedere all'elemento i di a.
Questo se i è maggiore della lunghezza scatena un' eccezione.

In teoria il check i < a.length dovrebbe avvenire sempre prima di accedere ad a[i]; un po come se facessi

if (i < a.length)
if (a[i] <= v)

in questo modo non ci può essere mai una eccezione.

Per fortuna questa cosa la puoi fare anche usando semplicemente l'operatore and, dato che Java come quasi tutti i linguaggi imperativi "ottimizza" l'operatore and; infatti and restituisce true solo se entrambi gli operandi sono true; questo vuol dire che se già il primo restituisce false, allora qualunque sia il valore del secondo il risultato sara comunque false.
Per questo motivo se il primo degli operandi è false il secondo non viene neanche controllato.

Tu puoi sfruttare questa cosa considerando che quando viene controllato il secondo operando dell'and, hai la garanzia che il primo è già risultato true (in questo caso hai la garanzia che i rispetta la lunghezza dell'array).

La stessa cosa vale per l'or, per cui se il primo è true, il secondo non viene controllato.

Nukles
12-01-2005, 09:07
grazie al tuo prezioso aiuto, finalmente ci sono riuscito: ecco il programma definitivo


/* Applicazione che ricevendo come parametro un array a non nullo di interi
ordinato in modo non decrescente e un valore intero v, crea e restituisce un nuovo array
di interi che contiene il valore v e tutti gli elementi di a, ed è ordinato in modo
non decrescente*/

class Inserimento2 {
public static int[] inserisci(int[] a, int v) {
// pre: a[i] >= a[i-1] && a != null
int i; //contatore
int k; //posizione in cui inserire il nuovo elemento
int[] b; //array di uscita

b = new int[a.length + 1];

k = 0;

/*istruzione di inserimento elementi nel nuovo array*/
for (i=0; i < a.length && a[i] <= v; i++) {
b[i] = a[i];
k++;
}

b[k] = v;

for (i = k; i<a.length; i++)
b[i+1] = a[i];

return b;

}

private static void visualizza(int[] a) {

/* metodo che visualizza il nuovo array */
int i; //indice di scansione di a

for (i=0; i<a.length; i++)
System.out.print(a[i] + " ");

System.out.println();
}

public static void main(String args[]) {
int i; //contatore

/*test di Inserimento*/
System.out.println("Test per int[] inserimento(int[] a, int v)");
/*nuovo elemento in prima posizione*/
visualizza( inserisci(new int[] {7, 8, 22}, 1) ); // (1, 7, 8, 22)
/*nuovo elemento in ultima posizione*/
visualizza( inserisci(new int[] {7, 8, 10}, 22) ); // (7, 8, 10, 22)
/*nuovo elemento in posizione intermetdia*/
visualizza( inserisci(new int[] {3, 4, 8, 10}, 5) ); // (3, 4, 5, 8, 10)
/*un solo elemento nell'array, numero da inserire maggiore*/
visualizza( inserisci(new int[] {8}, 10) ); // (8, 10)
/*un solo elemento nell'array, numero da inserire minore*/
visualizza( inserisci(new int[] {8}, 3) ); // (3, 8)

}

}


Ho tolto l'istruzione che mi avevi messo te


if (i == a.length) k++


perchè non era necessaria, o meglio, non era corretta nel programma che volevo fare io.



Tuttavia vorrei ulteriormente porti l'attenzione su un altro codice che svolge lo stesso fine di Inserimento2, ma fatto con 3 cicli: il primo che memorizza la posizione del nuovo elemento, il secondo che provvede a inserire gli elementi fino alla posizione, il terzo oltre quella posizione; te lo scrivo perchè mi dice ancora IndexOutOfBounds...



/* Applicazione che ricevendo come parametro un array a non nullo di interi
ordinato in modo non decrescente e un valore intero v, crea e restituisce un nuovo array
di interi che contiene il valore v e tutti gli elementi di a, ed è ordinato in modo
non decrescente*/

class Inserimento {
public static int[] inserisci(int[] a, int v) {
// pre: a[i] >= a[i-1] && a != null
int i; //contatore
int[] b; //array di uscita
int pos; //posizione del nuovo elemento

b = new int[a.length + 1];

pos = -1;

for (i=0; i<a.length; i++) {
if ( (pos == -1) && (a[i]>v) ) pos = i;
}

for (i=0; i<a.length && i<pos; i++) {
b[i] = a[i];
}

b[pos] = v;

for (i=pos+1; i<b.length; i++) {
b[i] = a[i-1];
}

return b;

}

private static void visualizza(int[] a) {

/* metodo che visualizza il nuovo array */
int i; //indice di scansione di a

for (i=0; i<a.length; i++)
System.out.print(a[i] + " ");

System.out.println();
}

public static void main(String args[]) {
int i; //contatore

/*test di Inserimento*/
System.out.println("Test per int[] inserimento(int[] a, int v)");
/*nuovo elemento in prima posizione*/
visualizza( inserisci(new int[] {7, 8, 22}, 1) ); // (1, 7, 8, 22)
/*nuovo elemento in ultima posizione*/
visualizza( inserisci(new int[] {7, 8, 10}, 22) ); // (7, 8, 10, 22)
/*nuovo elemento in posizione intermetdia*/
visualizza( inserisci(new int[] {3, 4, 8, 10}, 5) ); // (3, 4, 5, 8, 10)
/*un solo elemento nell'array, numero da inserire maggiore*/
visualizza( inserisci(new int[] {8}, 10) ); // (8, 10)
/*un solo elemento nell'array, numero da inserire minore*/
visualizza( inserisci(new int[] {8}, 3) ); // (3, 8)

}

}



ora ho fatto come mi hai suggerito, ovvero ho inserito il controllo della fine dell'array prima di tutti, ma purtroppo mi dà nuovamente errori di indice... secondo te a che cosa è dovuto? Sto cercando di trovare l'errore anche io...

Grazie mille

end.is.forever
12-01-2005, 10:32
Originariamente inviato da Nukles

public static int[] inserisci(int[] a, int v) {
// pre: a[i] >= a[i-1] && a != null
int i; //contatore
int[] b; //array di uscita
int pos; //posizione del nuovo elemento

b = new int[a.length + 1];
pos = -1;

for (i=0; i<a.length; i++) {
if ( (pos == -1) && (a[i]>v) ) pos = i;
}

for (i=0; i<a.length && i<pos; i++) {
b[i] = a[i];
}

b[pos] = v;

for (i=pos+1; i<b.length; i++) {
b[i] = a[i-1];
}

return b;

}


Qui il codice è insicuro per il motivo opposto: un array a cui accedi con indice minore di 0.
Infatti se guardi c'è la possibilità che pos che è inizializzato a -1 non venga mai modificato durante il primo for e resti -1.
In realtà questo valore non ha più senso dopo quel for, e dovresti prevedere un controllo successivo, cioè fare si che pos alla fine abbia in qualsiasi caso il valore giusto.
In questo caso il valore giusto è "a.length" se pos vale -1 dopo il for, infatti solo nel caso in cui tutti gli elementi dell'array siano minori di v pos non viene mai cambiato e resta -1, e quando è così tu vuoi inserire v nell'ultima posizione di b, cioè "a.length".

Quindi riassumendo

public static int[] inserisci(int[] a, int v) {
// pre: a[i] >= a[i-1] && a != null
int i; //contatore
int[] b; //array di uscita
int pos; //posizione del nuovo elemento

b = new int[a.length + 1];
pos = -1;

for (i=0; i<a.length; i++) {
if ( (pos == -1) && (a[i]>v) ) pos = i;
}


if (pos == -1)
pos = a.length;


for (i=0; i<a.length && i<pos; i++) {
b[i] = a[i];
}

b[pos] = v;

for (i=pos+1; i<b.length; i++) {
b[i] = a[i-1];
}

return b;

}


Potrebbero esserci altri errori a cui non ho fatto caso, se non va fammi sapere
Ciao

Nukles
12-01-2005, 10:43
Ecco perfetto grazie alla modifica che mi hai fatto fare ora anche questa versione funziona! Grazie mille...

Insomma, visto il motivo dell'errore, penso che se non ci fossi stato te avrei potuto continuare all'infinito a cercare l'errore, senza trovarlo... infatti guardavo tutt'altre cose.

Ti ringrazio molto, il tuo aiuto mi è servito a tanto. Quindi il valore di "index out of bounds" mi dice la posizione dell'indice fuori dall'array, vero? Posizione non contemplata nella lunghezza dello stesso...

end.is.forever
12-01-2005, 11:32
Figurati :)

In Java quando si verifica un errore viene in realtà scatenata un'eccezione; un'eccezione non è altro che un oggetto di classe Exception che descrive l'errore.
Questo serve a fare in modo che non ci sia bisogno di restituire valori strani (tipo -1 o una stringa "errore") quando un metodo si trova in condizioni di errore, ma semplicemente di lanciare un'eccezione non appena si verifica.

Per esempio se devi fare un metodo che calcoli la radice quadrata di un numero reale positivo:

public double radice(double numero) throws RadiceException
{
if (numero < 0)
throw new RadiceException("Numero negativo" , numero);
else
return Math.sqrt(numero);
}


Una cosa simile avviene quando tu accedi ad una indice fuori dai limiti di un'array: qualcuno (sotto da qualche parte) si accorge di questo e ti lancia un'eccezione che significa che l'operazione che hai richiesto non è stata possibile, e che descrive (con una stringa di messaggio data da getMessage() oppure con altri campi dato che Exception si può estendere e quindi realizzare Exception specializzate) qual'è stato l'errore.

L'eccezione, una volta scatenata (per esempio dal metodo radice di sopra), esce direttamente dal metodo (un po come se facesse un return ma senza dare un valore) e continua ad uscire da tutti i metodi chiamanti (per esempio se tu avessi chiamato radice da dentro un metodo "calcolaRadice" uscirebbe anche da calcolaRadice dal punto in cui viene chiamato radice) finchè non trova un blocco "try-catch".

Il blocco try-catch definisce che tipo di eccezione bloccare, per esempio

try
{
radice = calcolaRadice(input);
}catch (RadiceException e)
{
System.out.println( "Il numero inserito (" + e.numero + ") è negativo" );
}

e dice anche che cosa fare nel caso si verifichi quella eccezione.
Se l'eccezione è bloccata il programma poi continua subito dopo il blocco "try-catch".

ArrayIndexOutOfBoundsException è una classe che estende Exception, quindi un particolare tipo di eccezione, che dice appunto che l'errore verificatosi è un indice fuori dalle dimensioni.
Se questo può far parte della dinamica del tuo programma (ma in teoria non dovrebbe dato che l'algoritmo dovrebbe essere fatto in modo da rispettare le dimensioni) tu potresti semplicemente "captare" l'eccezione mettendo le operazioni di accesso agli array in un try-catch e comportarti di conseguenza quando questo succede.
Ciao

end.is.forever
12-01-2005, 11:43
Ah un' ultima cosa: per trovare più rapidamente gli errori, quando si scatena un'eccezione non gestita, cioè un'eccezione che non viene "captata" da nessun try-catch ed esce quindi dal programma dando un errore, stampa su standard error (e quindi di solito sulla tua console dos) il testo dello stack trace dell'eccezione.

Lo stack trace è una lista di tutti i metodi (dal più esterno al più interno) attraverso cui l'eccezione si è propagata, e per ognuno dice anche la linea di codice esatta da cui è "arrivata".

Per esempio nel tuo caso vedresti una serie di metodi, tra i quali (in mezzo probabilmente) compare anche il tuo metodo "inserisci", con a fianco il numero della riga di codice in cui è uscita l'eccezione (cioè quella dove fai b[pos] = v immagino).

Se usi un editor di testo normale tipo notepad trovare la riga nel codice è lungo (devi contare i numeri delle righe), mentre se usi un'ambiente di sviluppo apposta (una IDE) tutto questo è automatizzato.
Per esempio se usi Eclipse (che trovi qui (http://www.eclipse.org/) gratis) appena si verifica un'eccezione non gestita come la tua ti compare nella console lo stacktrace dell'eccezione e facendo doppio click (mi pare) sopra ti manda direttamente alla riga di codice del file giusto.

Se non ne usi già una ti consiglio Eclipse; poi naturalmente una IDE ti aiuta in molte altre cose come per esempio a fare il debug, l'indentazione, controlla automaticamente la sintassi mentre scrivi ecc...