appunti java pag.39 6. Acquisizione dati da tastiera Si sarà notato che gli esempi proposti non acquisiscono mai dati da tastiera ma solo per assegnamento o generazione random di numeri. Tutto questo è imputabile al fatto che, per eseguire una acquisizione dati da tastiera, si rende necessario introdurre le classi di Input e Output di Java. In secondo luogo Java è un linguaggio utile per realizzare interfacce di input di carattere grafico ovvero è più semplice acquisire dati in un campo all’interno di una finestra video (vedi ambiente windows) che non direttamente da console o ambiente DOS come usualmente opera il Pascal. Si farà un semplicissimo esempio di acquisizione dati da tastiera senza approfondire tutti i particolari di questo metodo perché è scarsamente utilizzato in un programma Java come si vedrà nei successivi sviluppi. 6.1. Uso dell’input da console (tastiera) Le classi di Input e output sono contenute nel package java.io l’informazione può essere utile anche se di tali classi si farà un uso limitatissimo e non si entrerà nel dettaglio. L’unica istruzione di output utilizzata fino ad ora è stata la System.out.println( ) oppure print( ): la prima equivale alla Writeln() del Pascal e la seconda alla Write( ). Nel seguito si farà uso di una istruzione equivalente alla Read() del Pascal la System.in.read( ). Questa è in grado di leggere solo semplici caratteri o byte da tastiera. Iniziamo da un esempio. esempio 6.1 : Si codifichi il seguente problema semplice “costruire un programma che acquisisca la dimensione N dall’utente, generi un array di N interi e assegni ad ogni componente i valori 100, 101 … 100+N-1 e lo stampi”. Richieste: a) Rispettare la seguente scomposizione ad albero: • main() chiama leggi() che restituisce un intero, genera il vettore richiesto e lo stampa. • leggi() : acquisisce da tastiera un intero che sarà la dimensione N del vettore; b) realizzare il main() program. main( ) leggi( ) c) realizzare le procedure leggi() e alt(); Prima codifica del main( ) appunti java pag.40 Commenti alla codifica La prima stesura del codice mostra il main che non contiene elementi nuovi, se si esclude l’invocazione di leggi(). Questa procedura (più esattamente metodo statico) é scritta in forma di prototipo vuoto. Anche in queste condizioni il programma può essere compilato per testare gli eventuali errori. L’intestazione della function ha il seguente significato: int leggi() – significa che la leggi() non riceve dati di input ma restituisce un intero (int) in output. Servirà per acquisire un numero che corrisponde alla dimensione del vettore da generare nel main(). Prosecuzione della codifica: codice di leggi(). Le nuove parole chiave sono: import java.io.*; che importa il package di i/o per usare il metodo di lettura da tastiera System.in.read(). throws IOException se non si aggiunge questa istruzione il compilatore segnala errore (Exception). Per ora la si interpreti semplicemente come un ordine al compilatore di non segnalare errori in sede di acquisizione dati. Vedremo in seguito cosa sono le eccezioni. (Notare che il throws è ripetuto nel main()). throws NumberFormatException (tralascia le segnalazioni di eccezione sul formato dei numeri) se non si aggiunge questa istruzione il compilatore segnala errore. Per ora interpretiamola semplicemente come un ordine al compilatore di non segnalare errori in sede di trasformazione di una stringa in intero operata dal metodo r= Integer.parseInt(s); La chiamata di questo metodo statico associato agli oggetti Integer trasforma la stringa s di input in un int r restituito in output. Il ciclo while (<cond>) { <blocco> }; - acquisisce singoli caratteri da tastiera con la System.in.read() e li pone nella stringa s fino al RETURN=‘\n’. appunti java pag.41 s=s+String.valueOf(c); è un metodo statico associato agli oggetti Stringa che, applicato al carattere (c), lo trasforma in una stringa che poi appende a s. s=s.substring(in, fin); è un metodo associato agli oggetti Stringa che, applicato alla stringa s con parametri interi (in, fin), restituisce la sottostringa da in a fin e la assegna a s stessa. (Serve ad eliminare da s il carattere di RETURN appeso dal ciclo precedente). Si può notare che le istruzioni del main() i=leggi(); V=new int[i]; consentono di dimensionare in corsa il vettore. A differenza del linguaggio Pascal i vettori di Java hanno un comportamento dinamico. Il Run da console Dopo la compilazione del programma si passa all’esecuzione che avverrà (a differenza dell’esecuzione dei programmi precedenti) con l’opzione: Build: Run with Console e non con l’opzione Build: Rum application. La ragione dell’uso di questa diversa opzione di RUN risiede nel fatto che per la prima volta il programma non deve mostrare solo un OUTPUT ma anche acquisire in INPUT; questo impone l’uso della finestra di tipo Console. Di seguito si nota la console di Input Output con i risultati: appunti java 6.2. pag.42 Parametri in Pascal e in Java Si è visto che per codificare un sottoproblema in Pascal si utilizza lo strumento della Procedure e della Function. Tale strumento è utile per stabilire quali dati di input riceve e quali dati in output deve restituire il sottoproblema individuato. In Java è definito il concetto equivalente di Metodo e lo si è usato con analoghe modalità. Per Metodo si intende una funzione che riceve dati di input, dal programma invocante, esegue certe operazioni su uno o più oggetti predefiniti e restituisce al termine, eventuali dati di output al programma che lo ha invocato. L'interfaccia o intestazione di un metodo Java trova quindi una corrispondenza nelle intestazioni di procedure o function del Pascal. Di seguito si cercheranno di individuare criteri tesi a stabilire l'esatta corrispondenza tra questi strumenti di codifica nei due linguaggi e di conseguenza permettere di trasformare procedure del Pascal in Metodi di Java. Si è fatto uso di metodi Java statici (static) e di metodi dinamici il cui diverso funzionamento è stato schematizzato con un diverso utilizzo della memoria del computer per l'allocazione dei valori assegnati. Si tratta ora di capire come questi due tipi di strumento possano essere usati correttamente per risolvere sottoproblemi. In sintesi si potrebbe dire che un metodo statico corrisponde al concetto di Function (o procedure) del Pascal in quanto può essere interpretato come uno spezzone di codice che riceve dati di input e restituisce risultati in output senza essere vincolato a nessuna Classe o Oggetto dinamico. Un Metodo dinamico è invece sempre definito in una Classe e viene sempre applicato ad una sua istanza (Oggetto). Gli oggetti possono essere manipolati solo con l'invocazione di questi metodi dinamici. Vediamo queste differenze attraverso esempi. Il sottoproblema, ormai risolto tante volte, “che calcola il Massimo Comun Divisore tra due numeri Naturali”, potrebbe essere schematizzato con le seguenti intestazioni: Pascal Intestazione sottoproblema Function MCD (a, b :integer):integer; Esempio di invocazione Var c:integer; c:=MCD(12, 18); Java public static int MCD( int a, int b); int c=MCD(12, 18) Java public (*)Naturale MCD(Naturale b); Naturale a, b, c; a = new Naturale(12); b = new Naturale(18); c = a.MCD(b); (b.1) (*)Se esistesse la Classe Naturale in Java (a.1) Nei tre casi precedenti si può interpretare l’intestazione di MCD() come una "entità" che riceve due numeri interi (a,b) e restituisce l’intero o il Naturale (c). a, b MCD c appunti java pag.43 La terza riga della tabella individua un terzo caso puramente teorico, in quanto la classe Naturale non esiste in Java, ma serve per mostrare che l'invocazione di un metodo dinamico in Java ha una forma diversa e deve sempre essere chiamato anteponendo all'identificatore del metodo l'oggetto a cui è associato: in questo caso è un Naturale. <oggetto> . metodo( <parametri> ); Se ci si sofferma sul significato dei parametri di invocazione di un metodo si nota che l'intestazione può avere due forme: (a) Metodo statico intestazione: public static <tipo par. out> nome_metodo ( <par. input> ); (a.1) invocazione di metodo statico definito all’interno di una classe: ris = nome_metodo(<par. Input>); (a.2) invocazione metodo statico associato ad una classe: ris = <Classe>.nome_metodo(<par. Input>); (b) Metodo dinamico intestazione: public < tipo par. out > nome_metodo ( <par. input> ); (b.1) invocazione metodo dinamico ris = <Oggetto>.nome_metodo(<par. input>); Quali sono nei due casi (a) e (b) i parametri di input e quelli di output ? Nel metodo statico (a.1) l'invocazione è analoga a quella di una Function del Pascal. I parametri di input sono tutti e solo quelli che compaiono entro le parentesi tonde (<par. input>), quelli restituiti devono sempre comparire raggruppati in una unica variabile (eventualmente strutturata) il cui tipo viene dichiarato davanti all'identificatore del metodo <tipo par. out>. I metodi int leggi() e void alt() dell’esempio precedente erano di questo tipo. Nel metodo statico associato ad una classe (a.2) l'invocazione richiede che si anteponga all'identificatore del metodo la corrispondente class di Java <classe>. I parametri di input di output sono esattamente gli stessi caso (a.1). I metodi usati nell’esempio precedente: int i = Integer.parseInt(String); String s = String.valueOf( char ); erano del tipo (a.2). Nel metodo dinamico, sempre associato a una classe (b.1) l'invocazione richiede che si anteponga all'identificatore del metodo un oggetto <oggetto> allocato in precedenza con new. I parametri di input sono tutti quelli che compaiono entro le parentesi tonde, a cui si deve aggiungere l'oggetto con cui è invocato. Quelli di output sono quindi identici a quelli del precedente caso (a.1) I metodi di String e StringBuffer del capitolo precedente: String S = A.substring(int, int); è applicato all’aggetto A che deve essere allocato e riceve in input i due parametri di tipo int, restituisce in output la sottostringa S. appunti java Se A,B sono due StringBuffer allocati con caratteri, l’invocazione pag.44 StringBuffer C = A.appen(B); Applica all’oggetto referenziato A l’operazione di append dell’oggetto in input B e restituisce l’oggetto C modificato in output. Un sottoproblema “che determini la prima è l'ultima occorrenza di un carattere assegnato in stringa” ha le seguenti intestazioni: Pascal Java Java Intestazione sottoproblema Esempio di invocazione Procedure pri_ult(s : string; ch : char; Var p,u : Var p,u : integer; integer); pri_ult('banana', 'a', p,u); writeln(p," ", u); public static coppia pri_ult(String s, char ch); class coppia { int p, int u } public coppia pri_ult(ch:char); (*) (*)Se esistesse il metodo in String coppia c; c = pri_ult("banana", 'a' ); (a.1) System.out.println(c.p+" "+c.u); String s="banana"; coppia c; c = s.pri_ult('a'); (b.1) System.out.println(c.p+" "+c.u); Si nota che un metodo statico o dinamico restituisce sempre un solo tipo di dato, è di conseguenza sempre necessario definire il tipo di dato da restituire, sia che questo tipo di dato appartenga a quelli predefiniti del linguaggio, sia che si tratti di un tipo di dato strutturato di nostra definizione. Nel caso particolare siccome devono essere restituiti due interi si dovrà dichiarare una classe coppia che diviene il contenitore unico dei due valori restituiti in output dal metodo. Anche in questo caso i tre sottoproblemi possono essere interpretati dal seguente "schema" s, ch c Pri_ult (c) non è più un tipo semplice ma è costituito dalla coppia (c.p, c.u). Un sottoproblema che “elimini un carattere di posizione assegnata in una sequenza di caratteri” ha le seguenti intestazioni; Pascal Intestazione sottoproblema Esempio di invocazione Function deleteCharAt(s:string; ind:integer ):string; Var sr : string; sr := deleteCharAt ('fico', 3); Pascal Procedure deleteChatAt(Var s:string, ind:Integer); Java Java Var s:straing; s := 'fico'; deleteCharAt (s, 3); public static StringBuffer deleteCharAt StringBuffer sr, s; (StringBuffer s, int ind); s = new StringBuffer("fico"); sr = deleteCharAt(s, 3) (a.1) public StringBuffer deleteCharAt(int ind); (*) (*) metodo presente in StringBuffer StringBuffer sr, s; s = new StringBuffer("fico"); sr= s.deleteCharAt(ind); appunti java pag.45 6.E – Esercizi Realizzazione o Conversione di sottoproblemi (con dati assegnati nel programma) con acquisizione dell’input da tastiera. Uso dei metodi di String, StringBuffer, Integer, Float, Double per risolvere problemi. 6.1 (conversione 5.2) Si desidera costruire un programma che “acquisita da tastiera una stringa che contenga sia lettere che numeri determini e stampi quante sono le prime e quanti i secondi e stampi a video il risultato”. Richiesta: realizzare un main() che invochi una function leggi_stri() analoga a leggi_int() dell’esercizio svolto del paragrafo 6.1. e fare uso dei metodi di String e StringBuffer. 6.2 (conversione 5.3) Si desidera costruire un programma che “acquisita da tastiera una stringa che contenga sia lettere che numeri sostituisca tutti i numeri con in carattere ‘x’ e stampi vecchia e nuova stringa a video”. Richiesta: realizzare un main() che invochi una function leggi_stri() analoga a leggi_int() dell’esercizio svolto del paragrafo 6.1. e fare uso dei metodi di String e StringBuffer. 6.3 (conversione 5.1) Realizzare un programma che “acquisita da tastiera una stringa e un carattere determini quante volte il carattere ricorre nella stringa e stampi a video il risultato”. Richiesta: rispettare la seguente scomposizione ad albero in sottoproblemi: leggi stri() main() leggi char() conta() main() deve invocare in sequenza leggi_stri(), leggi_char(), conta() e quindi stampare sulla “console” la stringa, il carattere e la sua ricorrenza; leggi_stri() deve acquisire da tastiera la stringa (String o StringBuffer); leggi_char() deve acquisire da tastiera il carattere necessario; conta() deve restituire il conteggio delle ricorrenze. Nota: le “procedure” leggi_xx() possono essere realizzate in analogia a leggi_int() dell’esercizio svolto del paragrafo 6.1. 6.4 (conversione 5.4) Realizzare un programma che “acquisita da tastiera la dimensione N<=20 di un vettore (di Integer), lo generi in modo random con interi compresi tra 0 e 99. Stampi a video tutte le componenti dell’array e la somma.”. Richiesta: rispettare la seguente scomposizione ad albero in sottoproblemi: leggi dim() main() somma() stampa() appunti java pag.46 main() deve invocare in sequenza leggi_dim(), somma(), stampa(); leggi_dim() deve acquisire da tastiera la dim<=20 del vettore. somma() solo eseguire e restituire la somma, inviando gli opportuni parametri di input; stampa() deve ricevere i dati da stampare ed eseguire la stampa a video. Nota: la “procedura” leggi_dim() può essere realizzata in analogia a leggi_int() dell’esercizio svolto del paragrafo 6.1. 6.5 (conversione 5.7) Si desidera costruire un programma che generi un array di dimensione dim<=30 di oggetti Number, immettendo in modo casuale nelle componenti sia Integer compresi tra 1 e 30 che Double compresi tra 0.00000 e 0.99999 e infine stampi e verifichi la distribuzione generata. Richieste: rispettare la seguente scomposizione in procedure: leggi_dim() scelta() main() gen_int() gen_double() stampa() main(): invoca dim=leggi_dim() per acquisire la dimensione dim<=30 dell’array da allocare, alloca l’array di Number e quindi all’interno di un ciclo invochi ripetutamente la procedura scelta() e, a seconda del numero generato da questa, saranno invocate gen_int() o gen_double() per generare l’oggetto desiderato e assegnarlo alla componente dell’array. Infine, fuori dal ciclo, sarà invocata la procedura stampa() che mostrerà a video il contenuto dell’array. scelta(): restituisce casualmente (random) 0 oppure 1. Zero indica la necessità di invocare gen_int() per assegnare un Integer all’array, Uno di invocare gen_double() per assegnare un Double all’array; gen_int(): genera un int compreso tra 1 e 30; gen_double(): genera un double compreso tra 0.0000 e 0.9999. stampa(): stampa l’array. 6.6 Si desidera costruire un programma che generi un array di dimensione dim<=30 di oggetti Number, come il precedente 6.5. Oltre alla stampa delle componenti dell’array come il precedente, stampi a fianco di ogni componente il tipo di dato (la classe a cui appartiene). Vedi esercizio 5.8. Esempio di stampa: V[0] = 0.0073 Double V[1] = 23 Integer V[2] = 2 Integer Ecc.