L'objectiu d'aquest CodeLab és aprendre a llegir correctament i validar les dades d'entrada d'un programa.

Quan implementem un programa no tenim control sobre les dades d'entrada que rebrà. El desajust entre el tipus de dades que espera un programa i el tipus de dades que, efectivament, rep, és una font de errors molt comuna. Per exemple, un programa espera llegir de l'entrada un nombre de tipus enter i el que es troba és una cadena de text. Aquesta circumstància és molt comú quan les dades d'entrada les introdueix l'usuari amb el teclat, encara que també es pot donar quan es llegeixen les dades d'un fitxer o de la xarxa.

Una norma bastant acceptada és la de llegir sempre l'entrada com a text, i tractar de convertir-la al tipus desitjat dintre del programa.

En aquest CodeLab programarem una petita aplicació de gestió d'alumnes, i utilitzarem els mètodes de conversió de tipus que ofereix Java per a validar les dades.

Per últim, aprendrem també a com facilitar la introducció repetitiva de dades quan estem desenvolupant un programa i hem d'anar fent proves sobre el seu funcionament.

Inicia IntelliJ i crea un nou projecte "Java"..

Marca l'opció "Command Line App":

Posa-li de nom DadesEntrada

Utilitzarem aquesta petita aplicació de gestió per a aprendre a tractar la validació de dades. Copia-la al teu projecte.

import java.util.Scanner;

class Alumne {
   String nom;
   float nota;
}

public class Main {

   public static void main(String[] args) {
       Scanner scanner = new Scanner(System.in);

       Alumne[] alumnes = new Alumne[10];
       int posicio = 0;

       while(true) {
           System.out.println();
           System.out.println("GESTIO ALUMNES");
           System.out.println("a) Introduir alumne");
           System.out.println("b) Llistar alumnes");
           System.out.println();

           String opcio = scanner.nextLine();

           switch (opcio) {
               case "a":
                   alumnes[posicio] = new Alumne();

                   System.out.println("Nom: ");
                   alumnes[posicio].nom = scanner.nextLine();
                   System.out.println("Nota: ");
                   alumnes[posicio].nota = scanner.nextFloat();
                   scanner.nextLine(); // descartem l'intro de després del float

                   posicio++;
                   break;
               case "b":
                   System.out.println("Llistat d'alumnes");
                   for (int i = 0; i < posicio; i++) {
                       System.out.println(alumnes[i].nom + ": " + alumnes[i].nota);
                   }
                   break;
               default:
                   return;
           }
       }
   }
}

Prova l'aplicació.

Quan llegim dades de tipus String amb el mètode nextLine(), no hi ha cap restricció al tipus de dada que es pot llegir.

Però què passa si quan tractem de llegir la nota amb nextFloat() l'usuari s'equivoca i introdueix una lletra, per exemple?

Ens trobem amb un desajust amb les dades d'entrada (InputMismatchException).

Una possible solució bastant comú és llegir les dades sempre amb nextLine() i tractar de convertir-les dintre del nostre programa. Hauríem de fer una cosa així:

nota = Float.parseFloat(scanner.nextLine());

Ara bé, això no soluciona -de moment- el problema, ja que si l'usuari introdueix un text, el programa no fallarà en la lectura de dades, però sí en la conversió:

Ara l'error és en el format de número (NumberFormatException), ja que la cadena "abc" no es pot convertir a tipus float.

try-catch

Per a corregir aquest nou error hem de fer una petita introducció a la sentència de control try-catch.

La sentència try-catch, és bastant semblant a la sentència if-else.

El bloc de la sentència catch només s'executa si alguna instrucció del bloc try genera una exepció.

En el nostre exemple, podem posar en el bloc try la sentència que converteix l'String a float. Si aquesta sentència falla, s'executarà la sentència del bloc catch.

try {
   nota = Float.parseFloat(scanner.nextLine());
} catch (Exception e){
   System.out.println("La nota introduïda no és valida");
}

D'aquesta manera el nostre programa no finalitzarà amb l'exepció NumberFormatException sinò que mostrarà a l'usuari un missatge indicant que la nota introduïda no és vàlida.

Només queda una última consideració. El programa no fallarà però, amb quin valor quedarà la nota d'aquell alumne? Efectivament, quedarà amb un 0.0.

Caldrà donar la opció a l'usuari de tornar a introduir la nota fins que aquesta sigui vàlida. O dit en pensament computacional, caldrà tornar a demanar la nota a l'usuari mentre (while) sigui invàlida.

do {
   System.out.println("Nota: ");
   try {
       alumnes[posicio].nota = Float.parseFloat(scanner.nextLine());
       break;
   } catch (Exception e) {
       System.out.println("La nota introduïda no és valida");
   }
} while(true);

El programa complet queda finalment així:

import java.util.Scanner;

class Alumne {
   String nom;
   float nota;
}

public class Main {

   public static void main(String[] args) {
       Scanner scanner = new Scanner(System.in);

       Alumne[] alumnes = new Alumne[10];
       int posicio = 0;

       while(true) {
           System.out.println();
           System.out.println("GESTIO ALUMNES");
           System.out.println("a) Introduir alumne");
           System.out.println("b) Llistar alumnes");
           System.out.println();

           String opcio = scanner.nextLine();

           switch (opcio) {
               case "a":
                   alumnes[posicio] = new Alumne();

                   System.out.println("Nom: ");
                   alumnes[posicio].nom = scanner.nextLine();

                   do {
                       System.out.println("Nota: ");
                       try {
                           alumnes[posicio].nota = Float.parseFloat(scanner.nextLine());
                           break;
                       } catch (Exception e) {
                           System.out.println("La nota introduïda no és valida");
                       }
                   } while(true);

                   posicio++;
                   break;
               case "b":
                   System.out.println("Llistat d'alumnes");
                   for (int i = 0; i < posicio; i++) {
                       System.out.println(alumnes[i].nom + ": " + alumnes[i].nota);
                   }
                   break;
               default:
                   return;
           }
       }
   }
}

Cada cop que fas una modificació el codi, executes el programa i introdueixes les dades per veure com va funcionant el programa.

Això significa una valuosa pèrdua de temps, que podem evitar posant les dades d'entrada en un arxiu i llegint les dades directament de l'arxiu.

Crea un arxiu de text al directori arrel de projecte (New > File) i escriu les dades d'entrada en el fitxer:

Modifica la construcció de l'Scanner per a que en lloc de System.in, tingui com a origen el fitxer dades:

Scanner scanner = new Scanner(new File("dades"));

Es mostrarà un error subratllat en vermell. Polsa Alt+INTRO i selecciona l'opció "Add exception to method signature":

Ja està, això és tot el que has de fer, ara ja pots executar el teu programa i fer tantes proves com vulguis, sense haver d'introduir cada cop les dades.