Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Dal linguaggio C al linguaggio Java parte 1, Dispense di Programmazione Java

introduzione alla programmazione orientata agli oggetti e al linguaggio Java. Si assume che il lettore conosca il linguaggio C cosicchè le caratteristiche di Java che derivano direttamente dal C sono trattate il più rapidamente possibile

Tipologia: Dispense

2019/2020

Caricato il 09/03/2020

libertar
libertar 🇮🇹

4.5

(15)

18 documenti

1 / 27

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Dal linguaggio C al linguaggio Java
(Prima parte)
Riccardo Silvestri
15-3-2009 1-3-2010
Queste dispense sono intese fornire una introduzione alla programmazione orientata agli oggetti e al
linguaggio Java. Si assume che il lettore conosca il linguaggio C cosicchè le caratteristiche di Java che
derivano direttamente dal C sono trattate il più rapidamente possibile. L'approccio adottato è pragmatico
e concreto, nel senso che si preferisce introdurre una nuova astrazione sulla base di una esigenza
concreta piuttosto che fare l'opposto (prima l'astrazione e poi le applicazioni concrete). Quindi, gli
esempi e gli esercizi assumono un'importanza centrale per l'introduzione e la spiegazione di nuovi
concetti. Infine è bene sottolineare che, allo scopo di rendere rapida l'acquisizione di una buona
dimestichezza con la programmazione ad oggetti e con Java, alcuni aspetti non sono trattati con la
dovizia di dettagli che meriterebbero. Questo è giustificato dalla convinzione che una volta acquisita una
buona padronanza del linguaggio diventa poi più facile comprendere le sottigliezze non solo del
linguaggio stesso ma anche della programmazione ad oggetti.
Sommario della prima parte
Che cos'è Java
Breve storia di Java
La macchina virtuale
La piattaforma
Confronto con altri linguaggi
Le basi procedurali di Java
Il primo programma
Tipi primitivi, stringhe, variabili e operatori Stringhe Funzioni matematiche
Input & Output
Controllo del flusso
Esercizi Stringa_verticale Parole_verticali Vocali Tre_più_grandi Cornice Triple_Pitagoriche Pi_greco
Cifre->lettere Lettere->cifre Numeri_perfetti Sostituzione Monete Fattori_primi Palindrome
Frasi_palindrome Potenze Monete_sbilanciate
Struttura di un programma Java
Classi e oggetti
Orientamento agli oggetti
Classi e oggetti Campi Metodi Costruttori Campi e metodi statici Un esempio
La prima classe
Esercizi Metodi_che_accedono Strisce_orizzontali Scacchiera_su_misura Metti_alla_prova Piramidi
Piramidi_capovolte Date Date+ Differenza_di_date Date_in_stringhe Data_di_nascita Razionali Razionali+
Tipi, riferimenti e variabili Tipi primitivi e tipi riferimento Inizializzazioni e valori di default
Errori in compilazione e in esecuzione
Classi nidificate
Esercizi Errori Immutabilità Liste_di_interi Date_vicine Code_di_stringhe Pile_di_stringhe
Pile_di_interi&stringhe Espressioni
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b

Anteprima parziale del testo

Scarica Dal linguaggio C al linguaggio Java parte 1 e più Dispense in PDF di Programmazione Java solo su Docsity!

Dal linguaggio C al linguaggio Java

(Prima parte)

Riccardo Silvestri

Queste dispense sono intese fornire una introduzione alla programmazione orientata agli oggetti e al

linguaggio Java. Si assume che il lettore conosca il linguaggio C cosicchè le caratteristiche di Java che

derivano direttamente dal C sono trattate il più rapidamente possibile. L'approccio adottato è pragmatico

e concreto, nel senso che si preferisce introdurre una nuova astrazione sulla base di una esigenza

concreta piuttosto che fare l'opposto (prima l'astrazione e poi le applicazioni concrete). Quindi, gli

esempi e gli esercizi assumono un'importanza centrale per l'introduzione e la spiegazione di nuovi

concetti. Infine è bene sottolineare che, allo scopo di rendere rapida l'acquisizione di una buona

dimestichezza con la programmazione ad oggetti e con Java, alcuni aspetti non sono trattati con la

dovizia di dettagli che meriterebbero. Questo è giustificato dalla convinzione che una volta acquisita una

buona padronanza del linguaggio diventa poi più facile comprendere le sottigliezze non solo del

linguaggio stesso ma anche della programmazione ad oggetti.

Sommario della prima parte

Che cos'è Java

Breve storia di Java

La macchina virtuale

La piattaforma

Confronto con altri linguaggi

Le basi procedurali di Java

Il primo programma

Tipi primitivi, stringhe, variabili e operatori Stringhe Funzioni matematiche

Input & Output

Controllo del flusso

Esercizi Stringa_verticale Parole_verticali Vocali Tre_più_grandi Cornice Triple_Pitagoriche Pi_greco

Cifre->lettere Lettere->cifre Numeri_perfetti Sostituzione Monete Fattori_primi Palindrome Frasi_palindrome Potenze Monete_sbilanciate

Struttura di un programma Java

Classi e oggetti

Orientamento agli oggetti

Classi e oggetti Campi Metodi Costruttori Campi e metodi statici Un esempio

La prima classe

Esercizi Metodi_che_accedono Strisce_orizzontali Scacchiera_su_misura Metti_alla_prova Piramidi

Piramidi_capovolte Date Date+ Differenza_di_date Date_in_stringhe Data_di_nascita Razionali Razionali+

Tipi, riferimenti e variabili Tipi primitivi e tipi riferimento Inizializzazioni e valori di default

Errori in compilazione e in esecuzione

Classi nidificate

Esercizi Errori Immutabilità Liste_di_interi Date_vicine Code_di_stringhe Pile_di_stringhe

Pile_di_interi&stringhe Espressioni

Che cos'è Java

Java è un linguaggio di programmazione orientato agli oggetti ( Object Oriented ) con una sintassi

simile a quella dei linguaggi C e C++. È un linguaggio potente che evita però quelle complesse

caratteristiche che rendono poco maneggevoli altri linguaggi orientati agli oggetti come il C++.

Breve storia di Java

Nel 1991 un team di ingegnieri della Sun Microsystems, guidato da Patrick Naughton e James Gosling,

iniziò la progettazione di un linguaggio con l'obbiettivo di agevolare la programmazione di piccoli

apparecchi elettronici. Siccome tali apparecchi non avevano grandi quantità di memoria e le CPU

potevano essere le più diverse, era importante che il linguaggio producesse codice snello e che non fosse

legato ad una particolare architettura hardware. Questi requisiti portarono il team ad ispirarsi ad un

modello che era stato adottato da alcuni implementatori del linguaggio Pascal ai tempi dei primi personal

computer. Infatti, l'inventore del Pascal, Niklaus Wirth era stato il primo a introdurre l'idea di un

linguaggio portabile basato sulla generazione di codice intermedio per un computer ipotetico detto

macchina virtuale ( virtual machine ). Gli ingegnieri del team adottarono questo modello ma basarono il

nuovo linguaggio sul C++ piuttosto che sul Pascal (questo perché la loro formazione era radicata nel

mondo UNIX). All'epoca, comunque, il nuovo linguaggio non era visto come un fine ma solamente come

un mezzo per la programmazione di piccoli apparecchi elettronici. Inizialmente Gosling pensò di

chiamarlo "Oak" (quercia) ispirato da una grande quercia che poteva ammirare dal suo studio. Ma, presto

si accorsero che esisteva già un linguaggio con quel nome. Il nome Java fu poi suggerito durante una

pausa in un coffee shop.

Intanto, agli inizi degli anni '90, il mercato degli apparecchi elettronici "intelligenti" non era ancora

sufficientemente sviluppato e l'intero progetto rischiava il fallimento. Però, il World Wide Web e internet

stavano crescendo a ritmi elevatissimi e il team si rese conto che la nascente tecnologia dei browser

poteva essere notevolmente potenziata proprio da un linguaggio con le caratteristiche di Java

(indipendente dall'architettura hardware, real-time, affidabile e sicuro). Nel 1995 alla conferenza

SunWorld presentarono il browser HotJava scritto interamente in Java che poteva eseguire codice (Java)

contenuto in pagine web (ciò che oggi è chiamato applet ). Agli inizi del 1996 la Sun rilasciò la prima

versione di Java e il linguaggio iniziò a suscitare un interesse crescente. La versione 1.0 di Java non era

certo adeguata per lo sviluppo serio di applicazioni. Ma da allora il linguaggio, attraversando parecchie

versioni, è stato via via arricchito e le sue librerie sono state enormemente potenziate ed ampliate fino

alla più recente versione 6, rilasciata nel 2006. Attualmente Java è un linguaggio maturo ed è usato per

sviluppare applicazioni su grande scala, per potenziare le funzionalità di server web, per fornire

applicazioni per apparecchi elettronici di largo consumo come cellulari e PDA e per tanti altri scopi.

La macchina virtuale

Quando un programma scritto in Java è compilato, il compilatore lo converte in bytecodes che sono le

istruzioni di un linguaggio macchina di una CPU virtuale chiamata appunto Macchina Virtuale Java

( Java Virtual Machine ), in breve JVM. La JVM non corrisponde a nessuna CPU reale anche se, in linea

di principio, potrebbe essere realizzata direttamente in hardware. La JVM è sempre implementata sotto

forma di un software che esegue le istruzioni bytecodes tramite un opportuno interprete. Ovvero la JVM

traduce i bytecodes nelle istruzioni macchina della CPU del computer reale sul quale si vuole eseguire il

programma Java. Quindi per poter eseguire un programma Java su un certo sistema hardware e software

(ad esempio, un PC Pentium Intel con Mac OS X) è necessario vi sia installata una specifica JVM

(capace di eseguire la traduzione per quel sistema).

A questo punto ci si potrebbe chiedere quali sono i vantaggi di questo passaggio per un linguaggio

Esistono altri linguaggi simili a Java. Sicuramente quello che più di tutti è simile a Java è il C#. Però

questo linguaggio a differenza di Java, e anche del C e del C++, non è disponibile per sistemi operativi

diversi da Windows.

Le basi procedurali di Java

Presentare ogni aspetto del linguaggio Java fino ad un adeguato livello di approfondimento, porta

inevitabilmente a posticipare molti argomenti importanti e a frammentare e ritardare una visione

d'assieme del linguaggio. E questo è tanto più vero per un linguaggio come Java che è molto più

complesso di un linguaggio come il C. Per questa ragione, dapprima faremo un tour delle principali

caratteristiche del linguaggio e poi ritorneremo su quelle che necessitano di un adeguato

approfondimento. Diamo per scontata una buona conoscenza del linguaggio C e quindi non ci

soffermeremo più dello stretto necessario sulle caratteristiche di Java che derivano direttamente da tale

linguaggio.

Il primo programma

In Java un programma è composto da classi. Per ora, una classe può essere vista come una struct del

C in cui però oltre ai campi è possibile definire anche delle funzioni che in Java sono chiamate metodi.

Come in C la definizione di una struct può essere usata solamente allocando i corrispondenti elementi

così in Java una classe per poter essere usata deve essere istanziata in oggetti. Tuttavia, in Java, come

vedremo presto, una classe può essere usata anche senza che venga istanziata. Anzi i primi esempi

riguarderanno proprio programmi che usano una classe senza istanziarla.

public class Primo { public static void main(String[] args) { System.out.println("Primo programma Java"); } }

L'effetto di questo programma è semplicemente quello di stampare a video la stringa "Primo programma

Java". In grassetto sono state evidenziate le parole chiave di Java. La parole chiave class inizia la

definizione di una classe. Questa e poi seguita dal nome della classe, in questo caso Primo. Poi tra

parentesi graffe è definito il corpo della classe, cioè tutti i suoi membri (campi e metodi). In questo caso,

c'è un solo metodo ed è un metodo speciale perchè può essere visto come il corrispettivo in Java della

funzione main del C. Infatti, l'esecuzione di un programma Java inizia sempre eseguendo il metodo main

di una classe. Il metodo main come qualsiasi altro metodo è definito dichiarando una intestazione

( method header ) e un corpo ( method body ) racchiuso tra parentesi graffe. L'intestazione a sua volta

comprende, nell'ordine, degli eventuali modificatori ( modifiers ), in questo caso public e static, il tipo

del valore ritornato (void), il nome del metodo (main) e la lista dei parametri (String[] args). Il

metodo main essendo speciale deve sempre avere l'intestazione che abbiamo visto. Vedremo in seguito il

significato dei modificatori e dei parametri.

Il corpo del main, in questo caso, contiene solamente la invocazione di un metodo. Si noti che abbiamo

usato il termine "invocazione" per indicare ciò che in C corrisponderebbe alla chiamata di una funzione.

Infatti questo è il termine che si usa in Java. Il metodo invocato è println() che appartiene all'oggetto

out che a sua volta è un campo della classe System. La classe System è una delle tantissime classi

predefinite della piattaforma Java. Per adesso basti dire che l'effetto di System.out.println("Primo

programma Java") è perfettamente simile a quello di printf("Primo programma Java\n") in C. Si

noti anche che in Java si usa lo stesso operatore di selezione "." del C per accedere ai campi e ai metodi

di una classe o di un oggetto.

In Java è richiesto che il file in cui è scritta una classe abbia lo stesso nome della classe. Se il nome

della classe è NomeClasse allora il file deve chiamarsi NomeClasse.java. Così nel nostro caso il file che

contiene la definizione della classe Primo deve chiamarsi Primo.java. Attenzione a rispettare le

maiuscule e minuscole perchè Java è un linguaggio sensibile a questa differenza in tutti i contesti: parole

chiave, nomi di variabili, classi, metodi, file, ecc. Quindi tutti i file che contengono codice sorgente in

Java devono avere l'estensione .java e il loro nome deve essere uguale al nome della classe definita nel

file. Più precisamente, in un file .java può essere definita una sola classe pubblica (identificata appunto

dal modificatore public), però può contenere anche la definizione di altre classi non pubbliche.

Tipi primitivi, stringhe, variabili e operatori

I tipi primitivi di Java sono simili a quelli del C ma con importanti differenze. La seguente tabella

descrive i tipi primitivi di Java:

boolean true o false

char carattere 16-bits Unicode UTF-16 (senza segno)

byte intero da 8 bits: da -128 a 127

short intero da 16 bits: da -32768 a 32767

int intero da 32 bits: da -2147483648 a 2147483647

long intero da 64 bits: da -9223372036854775808 a 9223372036854775807

float numero in virgola mobile da 32 bits (IEEE 754)

double numero in virgola mobile da 64 bits (IEEE 754)

I tipi numerici byte,short,int,long,float,double sono molto simili a quelli del C. Il tipo char può

essere visto come una estensione del corrispondente tipo del C e ne discuteremo fra poco. La

dichiarazione delle variabili e la loro inizializzazione ricalca la sintassi del C. Ecco alcune dichiarazioni

ed inizializzazioni:

byte interopiccolissimo = -2; short interopiccolo = 50; int interogrande = 120000; long interograndissimo = 345000000000000;

Come in C il simbolo "=" rappresenta l'assegnamento e il ";" termina ogni istruzione ( statement ). Anche

gli operatori sono gli stessi del C. Ad esempio, il seguente frammento di programma Java calcola gli

interessi maturati in un investimento di 1000 euro per 5 anni al tasso annuo del 4%:

int capitaleIniziale = 1000; //capitale iniziale double tasso = 1.04, tassoComposto5; // il tasso composto per 5 anni e' il tasso annuale elevato alla quinta potenza tassoComposto5 = tasso*tasso; tassoComposto5 *= tassoComposto5; tassoComposto5 = tasso; double capitaleFinale = capitaleInizialetassoComposto5; double interessi = capitaleFinale - capitaleIniziale;

Come si intuisce da questo esempio gli operatori aritmetici +,-,*,/,% hanno lo stesso significato che

hanno nel C, incluse le forme con assegnamento +=,-=,*=,/=,%= e gli operatori ++,-- di incremento e

decremento. Anche i commenti si scrivono nello stesso modo: // per quelli su una singola linea e /*

... */ per quelli che possono occupare più linee. Inoltre le conversioni automatiche tra i tipi numerici

seguono regole simili a quelle del C.

Il tipo char è differente dall'omonimo del C. Infatti è in grado di rappresentare oltre ai tradizionali

caratteri ASCII anche l'insieme molto più vasto dei caratteri Unicode. Le costanti carattere, come in C,

sono racchiuse tra apici singoli. Ad esempio 'A','a','0','w','@' rappresentano i corrispondenti

caratteri. Inoltre, possono anche essere usate le Unicode escape sequences. Queste sono sequenze del

tipo \uxxxx dove xxxx è un intero a 16 bits scritto in esadecimale. Ad esempio, '\u0041' è equivalente

ad 'A', '\u03C0' è il carattere pi greco minuscolo. Per informazioni complete sui codici Unicode si può

consultare il sito: http://www.unicode.org/. Oltre alle Unicode escape sequences che permettono di

definire tutti i caratteri rappresentabili, è possibile usare anche delle sequenze di escape simili a quelle

del C: \b (backspace), \t (tab), \n (line feed), \f (form feed), \r (carriage return), " (double quote), '

if (distanza(x, y, 1.0, 1.0) <= 1.0) // controlla se il punto puntiIn++; // cade nel cerchio unitario } System.out.println("Il valore di \u03C0 è "+Math.PI); double approxPI = (4*( double )puntiIn)/numeroPunti; System.out.println("Il valore approssimato è "+approxPI); } }

Come si vede il for, l'if e vari operatori hanno la stessa sintassi e lo stesso significato che hanno nel

linguaggio C (ritorneremo su di essi fra poco). L'esecuzione del programma produce il seguente risultato:

Il valore di π è 3. Il valore approssimato è 3. Input & Output

Le librerie della piattaforma Java forniscono gli strumenti per programmare interfacce utente grafiche,

GUI ( Graphical User Interface ), di tutti i generi da quelle più semplici a quelle più ricche e sofisticate.

Però l'uso di tali strumenti richiede una conoscenza del linguaggio Java piuttosto approfondita. Almeno

per il momento, dovremmo accontentarci dell'input/output forniti dalla cara e vecchia console. Per

l'output abbiamo già incontrato System.out.println() che permette di stampare sullo " standard output

stream " (cioè, la finestra della console). Per l'input, cioè, la lettura dallo " standard input stream ", la

situazione non è così semplice. L'analogo per l'input di System.out è System.in ma quest'ultimo

oggetto (che per la cronaca è di tipo InputStream) permette di leggere dallo standard input solamente al

livello dei bytes. Si può quindi intuire che se usassimo direttamente System.in per leggere, ad esempio,

un numero o una stringa dovremmo fare parecchio lavoro per tradurre il flusso di bytes nel

corrispondente dato (numero o stringa). Per fortuna, sempre la piattaforma Java, ci fornisce una classe

che fa proprio questa traduzione. La classe si chiama Scanner e per usarla è sufficiente creare un oggetto

di tipo Scanner che è "attaccato" al flusso di input:

Scanner in = new Scanner(System.in);

dell'operatore new e di come si costruisce un oggetto ne discuteremo in seguito. Per ora basti dire che

questa istruzione crea un oggetto di tipo Scanner basato su System.in e pone il riferimento a tale

oggetto nella variabile in. Gli oggetti di tipo Scanner hanno vari metodi che permettono di leggere il

flusso di input come numeri, parole, linee, ecc. Ad esempio,

String linea = in.nextLine();

legge la prossima linea dal flusso di input (cioè la sequenza di caratteri fino al prossimo separatore di

linea) e la pone in un oggetto stringa. Analogamente il metodo next() legge il prossimo token (sequenza

di caratteri delimitata da whitespaces) e i metodi nextInt() e nextDouble() leggono, rispettivamente, il

prossimo intero e il prossimo numero in virgola mobile (se presente).

Come esempio consideriamo un programma che calcola l'importo della rata per la restituzione di un

prestito avendo fornito in input il capitale, il tasso annuo e il numero complessivo di rate mensili. La rata

è calcolata applicando le formule:

RATA = CAPITALE *( TM * TC )/( TC - 1)

TM = (1 + TA /100)1/12^ - 1

TC = (1 + TM ) NR

inoltre TA è il tasso annuo e NR è il numero di rate. Così 100* TM risulta essere il tasso su base mensile e

100*( TC - 1) è l'interesse composto relativo all'intero periodo di restituzione del prestito.

import java.util.*; public class Rata { // metodo statico che calcola il tasso mensile a partire da quello annuo

public static double tassoMensile( double ta) { return 100(Math.pow(1 + ta/100, 1.0/12.0) - 1); } public static void main(String[] args) { Scanner in = new Scanner(System.in); // creazione dell'oggetto Scanner System.out.print("Capitale: "); int capitale = in.nextInt(); // legge l'importo del capitale System.out.print("Tasso annuo: "); double tassoAnnuo = in.nextDouble(); // legge il tasso annuo System.out.print("Numero rate mensili: "); int numeroRate = in.nextInt(); // legge il numero di rate double tassoMensile = tassoMensile(tassoAnnuo); System.out.println("Il tasso mensile è "+tassoMensile+"%"); double tm = tassoMensile/100; // calcola l'importo della double tc = Math.pow(1 + tm, numeroRate); // rata applicando la double rata = capitale((tm*tc)/(tc - 1)); // formula System.out.println("L'importo della rata è "+rata); } }

La linea import java.util.*; è necessaria perché la classe Scanner appartiene al package java.util.

Tutte le volte che si usa una classe che non appartiene al package di base java.lang (System, String e

Math appartengono a questo package) è necessario dichiarare il package di appartenenza tramite una

direttiva import. Parleremo più dettagliatamente dei packages e della direttiva import in seguito. Una

possibie esecuzione del programma produce il seguente risultato:

Capitale: 20000 Tasso annuo: 15 Numero rate mensili: 36 Il tasso mensile è 1.171491691985338% L'importo della rata è 684. Controllo del flusso

Tutte le istruzioni di Java per il controllo del flusso in un programma sono riprese da quelle del C, con

una sola eccezione che discuteremo più avanti. Quindi Java dispone delle istruzioni if-else, while, do-

while, for e switch-case con la stessa sintassi del C. Vediamo subito alcuni semplici esempi. Il

seguente programma prende in input tre numeri e li stampa ordinati in senso cresecente:

import java.util.*; public class Ordine { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.println("Digita tre numeri: "); double a = in.nextDouble(), b = in.nextDouble(), c = in.nextDouble(); String risultato = "I tre numeri ordinati sono "; if (a <= b) { if (b <= c) risultato += a + " " + b + " " + c; else if (a <= c) risultato += a + " " + c + " " + b; else risultato += c + " " + a + " " + b; } else { if (a <= c) risultato += b + " " + a + " " + c; else if (b <= c) risultato += b + " " + c + " " + a; else risultato += c + " " + b + " " + a; } System.out.println(risultato); } }

Come si intuisce da questo esempio anche tutti gli operatori relazionali <,<=,>=,>,==,!= sono uguali a

quelli del C. Il prossimo programma prende in input una stringa e conta il numero di vocali presenti nella

Scanner in = new Scanner(System.in); int n = in.nextInt(); String msg; switch (n) { case 1: msg = "Hai digitato 1"; break ; case 2: msg = "Hai digitato 2"; break ; case 3: msg = "Hai digitato 3"; break ; default : msg = "Hai digitato qualcosa di diverso da 1,2,3"; } System.out.println(msg); } }

Ovviamente, ritroveremo tutti questi costrutti per il controllo del flusso molto spesso nel seguito usati in

esempi ed esercizi. Inoltre, anche in Java è possibile scrivere metodi ricorsivi. Ecco un semplice

programma che implementa un metodo ricorsivo per il calcolo del fattoriale:

import java.util.; public class Fattoriale { public static long fattoriale( int n) { // metodo ricorsivo if (n <= 1) return 1; else return nfattoriale(n - 1); } public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Digita un intero: "); int n = in.nextInt(); System.out.println("Il fattoriale di "+n+" è "+fattoriale(n)); } } Esercizi

In alcuni esercizi può essere utile usare il metodo print() che è uguale a println() eccetto che non

va a capo. Inoltre, si tenga presente che entrambi i metodi accettano come parametro anche un singolo

char.

[Stringa_verticale] Scrivere un programma che legge una stringa (cioè una linea di testo) e la stampa

in verticale. Ad esempio, se la stringa letta è "gioco" allora il programma stampa:

g i o c o

[Parole_verticali] Scrivere un programma che legge tre parole e le stampa in verticale l'una accanto

all'altra. Ad esempio, se le parole sono "gioco", "OCA" e "casa" allora il programma stampa:

gOc iCa oAs c a o

[Vocali] Scrivere un programma che legge una linea di testo e per ogni vocale stampa il numero di

volte che appare nella linea di testo. Ad esempio, se la linea di testo è "mi illumino di immenso"

allora il programma stampa:

a: 0 e: 1 i: 5 o: 2 u: 1

[Tre_più_grandi] Scrivere un programma che legge una serie di numeri interi positivi (la lettura si

interrompe quando è letto un numero negativo) e stampa i tre numeri più grandi della serie. Ad esempio,

se la serie di numeri è 2, 10, 8, 7, 1, 12, 2 allora il programma stampa:

I tre numeri più grandi sono: 12, 10, 8

[Cornice] Scrivere un programma che legge un intero n e stampa una cornice quadrata di lato n fatta di

caratteri '*'. Ad esempio, se n = 5 , il programma stampa:

[Triple_Pitagoriche] Una tripla pitagorica è una tripla di numeri interi a , b , c tali che 1 ≤ a ≤ b ≤ c

e a^2 + b^2 = c^2. Ciò equivale a dire che a , b , c sono le misure dei lati di un triangolo rettangolo (da qui il

nome). Scrivere un programmma che legge un intero M e stampa tutte le triple pitagoriche con c ≤ M.

[Pi_greco] Scrivere un programma che letto un intero k stampa la somma dei primi k termini della serie

La serie converge al numero pi greco. Quanto deve essere grande k per ottenere le prime 8 cifre decimali

corrette (3.14159265)?

[Cifre->lettere] Scrivere un programma che legge un intero n e stampa le cifre di n in lettere. Ad

esempio, se n = 2127 , il programma stampa: due uno due sette.

[Lettere->cifre] Scrivere un programma che esegue la trasformazione inversa di quella del programma

precedente. Letta una linea di testo, se questa è composta di parole rappresentanti numeri da 1 a 9,

stampa il numero corrispondente. Ad esempio, se legge "due uno due sette" allora stampa 2127.

[Numeri_perfetti] Un numero perfetto è un numero intero che è uguale alla somma dei suoi divisori

propri, ad esempio 6 è perfetto perché 6 = 1 + 2 + 3, mentre 8 non è perfetto dato che 1 + 2 + 4 non fa 8.

Scrivere un programma che letto un intero M stampa tutti i numeri perfetti minori od uguali a M e le

relative somme dei divisori. Ad esempio se M = 1000 il programma stampa:

[Sostituzione] Scrivere un programma che legge una linea di testo e se questa contiene la parola

"mille" allora stampa la linea di testo in cui però tutte le occorrenze della parola "mille" sono

sostituite con la parola "cento". Ad esempio, se la linea di testo è "mille non più mille" allora il

programma stampa: "cento non più cento".

[Monete] Scrivere un programma che letto un numero intero rappresentante un importo in centesimi di

euro stampa le monete da 50, 20, 10, 5, 2 e 1 centesimi di euro che servono per fare l'importo. Ad

esempio, se l'importo è di 97 centesimi allora il programma stampa:

1 moneta da 50 2 monete da 20 1 moneta da 5 1 moneta da 2

[Fattori_primi] La scomposizione in fattori primi di un numero n è l'elenco dei fattori primi di n con le

loro molteplicità. Si ricorda che un fattore primo di n è un divisore di n che è un numero primo (un

numero è primo se non ha divisori propri). Scrivere un programma che legge un numero intero n e

stampa la scomposizione in fattori primi di n. Il comportamento del programma per alcuni valori di n è il

seguente:

Questi elementi devono apparire nell'ordine dato.

Classi e oggetti

Finora abbiamo visto quegli aspetti di Java che non sono dissimili da quelli di un qualsiasi linguaggio

di programmazione procedurale. Adesso iniziamo a entrare nel vivo del linguaggio Java, considerando

quelle caratteristiche che lo rendono un linguaggio orientato agli oggetti.

Orientamento agli oggetti

Cosa significa dire che un linguaggio è "orientato agli oggetti"? Per rispondere a questa domanda

conviene fare un passo indietro e ricordarsi qual'è l'obbiettivo di un linguaggio di programmazione. Il

principale obbiettivo è rendere facile la vita dei programmatori in tutte quelle fasi dello sviluppo del

software in cui il linguaggio di programmazione usato riveste un ruolo importante (ad esempio, nella

progettazione, nella scrittura del codice, nel debugging, nel testing e nella manutenzione del codice). E

come fa un linguaggio a tentare di raggiungere questo obbiettivo? Cercando di gestire al meglio la

complessità che è intrinseca in un qualsiasi sistema software di dimensioni non banali. E c'è,

essenzialmente, un solo modo per fronteggiare la complessità: cercare di decomporre l'intero in parti

meno complesse. I diversi linguaggi di programmazione usano filosofie e meccanismi differenti per

aiutare i programmatori ad attuare questa strategia.

I linguaggi procedurali, come ad esempio il C, offrono pochi mezzi: funzioni o procedure e la

possibilità di costruire nuovi tipi aggregando altri tipi (ad esempio, tramite le struct). Una procedura

permette di isolare una parte del sistema software dal resto. Così che il resto del sistema può

disinteressarsi di come è fatta la procedura al suo interno e considerare solamente ciò che serve per poter

usare la procedura (la sua interfaccia). Questo riduce la complessità riducendo il numero delle potenziali

relazioni fra le varie parti del sistema. Sostanzialmente, è il principio dell' information hiding o

incapsulamento : suddividere il sistema in parti in modo tale che le loro interazioni si possano definire in

base a semplici interfacce che risultano indipendenti da come le parti sono implementate. Questo

principio è così consolidato e naturale che ormai è quasi dato per scontato. Oltre al principio

dell'incapsulamento ci sono altri aspetti di un linguaggio che possono aiutare a fronteggiare la

complessità anche se non sono altrettanto importanti. La possibilità di costruire nuovi tipi tramite

semplice aggregazione di altri tipi aiuta a ridurre la complessità tramite la diminuzione della distanza tra

la natura delle informazioni reali e la loro rappresentazione nel sistema. Questo a sua volta migliora la

leggibilità del codice e quindi anche la capacità di modificare ed estendere le funzionalità del sistema.

I linguaggi orientati agli oggetti come Java offrono mezzi più sofisticati per fronteggiare la complessità

del software. Uno dei più importanti trae la sua forza dalla combinazione delle due caratteristiche sopra

menzionate. Infatti una classe può essere vista, in prima approssimazione, come una struct che oltre ad

avere campi ha anche delle funzioni che in Java si chiamano metodi. Così una classe è più efficace

nell'isolare una parte del sistema software perché può comprendere sia procedure sia dati che sono

intimamente connesse le une agli altri. Ad esempio, in un sistema software per la gestione dei dati del

personale di una azienda, ci potrebbe essere una classe Impiegato che serve a rappresentare e

manipolare i dati relativi agli impiegati. Questa classe conterrà, oltre ai soliti campi (nome, cognome,

data_di_nascita, ecc.), anche dei metodi: un metodo età() che calcola l'età attuale, un metodo

stipendio() che calcola la busta paga, un metodo stampa() per la stampa formattata dei dati

dell'impiegato, ecc. Ogni istanza della classe Impiegato, che in Java si chiama oggetto , rappresenta uno

specifico impiegato. Complessivamente i valori dei campi di un oggetto sono lo stato di quell'oggetto. Il

comportamento di un oggetto, cioè il risultato dell'invocazione di un qualsiasi metodo relativamente a

quell'oggetto, dipende dallo stato dell'oggetto. Così, se rossi è il riferimento all'oggetto che rappresenta

l'impiegato Mario Rossi, l'invocazione del metodo rossi.età() ritorna proprio l'età di Mario Rossi, così

come verdi.età() ritorna invece l'età di Giuseppe Verdi, se verdi è un riferimento all'oggetto che

rappresenta l'impiegato Giuseppe Verdi.

Tutto questo, oltre ad ampliare le possibilità di applicazione del principio dell'information hiding,

permette anche di migliorare la leggibilità e sopratutto la riusabilità del codice. La riusabilità è un aspetto

di grandissima importanza per rendere l'attività della programmazione più proficua ed efficiente. Quando

infatti la strategia della programmazione orientata agli oggetti è applicata alla realizzazione di librerie

software (ad esempio, manipolazione di stringhe, gestione di collezioni di elementi, accesso a file, ecc.)

mostra tutta la sua forza realizzando strumenti di uso generale che possono essere usati e riusati in

tantissime situazioni. Il successo e la continua crescita della piattaforma Java ne è una solida prova.

Nel linguaggio Java, al pari degli altri linguaggi orientati agli oggetti, il meccanismo base delle classi e

degli oggetti è coadiuvato da altri meccanismi. Tra i più importanti c'è il meccanismo dell' ereditarietà

che consente di estendere in modo naturale una classe (cioè, modificare o aggiungere funzionalità) per

definire altre classi. Questo meccanismo a sua volta permette il polimorfismo che è molto utile per

trattare in modo uniforme le funzionalità di oggetti appartenenti a classi differenti. E poi ci sono la

genericità e l' overloading.

Classi e oggetti

Per il momento vedremo solamente la versione base della definizione di una classe. Poi, mano a

mano, avremo modo di introdurre tutte le altre caratteristiche. Lo schema semplificato della definizione

di una classe pubblica può essere descritto così:

public class NomeClasse { dichiarazioni di campi dichiarazioni di costruttori dichiarazioni di metodi }

Il modificatore di accesso ( access modifier ) public indica proprio che la classe è pubblica, cioè è

visibile e quindi accessibile da qualsiasi altra parte del programma. Non c'è nessun vincolo sull'ordine

con cui sono elencate le dichiarazioni all'interno del corpo della classe. Di solito però sono disposte in

quell'ordine.

Campi Con il termine campo si intende una variabile che appartiene ad una classe e la dichiarazione di

un campo è simile alle dichiarazioni di variabili che abbiamo già incontrato. Però può essere preceduta

da dei modificatori, tra questi quelli che vedremo subito sono i modificatori di accesso public e

private. Ad esempio,

public double valore; private int status; int codice;

la prima dichiarazione riguarda una variabile valore di tipo double che è pubblica, cioè qualsiasi parte

del programma, anche al di fuori della classe e del package della classe, può accedere al campo valore,

cioè può leggerlo o scriverlo. Mentre la variabile status essendo dichiarata privata è accessibile

solamente dall'interno della classe in cui è definita. La variabile codice, non avendo alcun modificatore

di accesso specificato, è accessibile solamente dall'interno del package a cui appartiene la classe. Di

solito i campi di una classe sono dichiarati privati per evitare che dall'esterno della classe si possa

modificarne i valori senza che questo sia controllato dalla classe. Quindi l'uso del modificatore private

aiuta l'applicazione del principio dell'incapsulamento.

Metodi Un metodo è una funzione che appartiene ad una classe. La dichiarazione di un metodo rispetta

il seguente schema semplificato che comprende una intestazione e un corpo:

modificatori tipo-ritornato nomeMetodo ( lista-parametri ) { corpo-del-metodo }

L' intestazione del metodo è formata da uno o più modificatori, il nome tipo-ritornato del tipo del valore

ritornato, il nome del metodo e la lista dei parametri. Se il metodo non ritorna alcun valore allora il tipo-

ritornato è void, come nel C. La lista dei parametri può essere vuota ed è simile alla lista dei parametri

di una funzione del C. Inoltre, il passaggio dei parametri è, come nel C, per valore. La signature (firma)

secondi non dipendono da nessun oggetto della classe. I campi e i metodi statici possono essere visti

come campi e metodi condivisi da tutti gli oggetti della classe. Ad esempio, un campo statico potrebbe

mantenere un valore costante che è uguale per tutti gli oggetti della classe. Esempi di campi statici sono i

campi out e in della classe System o il campo PI della classe Math. Un metodo statico potrebbe essere

un metodo che combina in qualche modo due oggetti della classe creandone un terzo oppure un metodo

che non ha bisogno dello stato di un oggetto specifico per essere calcolato. Esempi di metodi statici sono

tutti i metodi della classe Math, come sqrt(), pow(), ecc. Per dichiarare un campo o un metodo statico si

usa il modificatore static.

Un esempio Consideriamo come esempio una semplice classe che rappresenta studenti. Ogni oggetto

della classe ha tre campi matricola, nome e cognome. Inoltre ha un costruttore e alcuni metodi pubblici.

La classe ha anche un campo statico matricolaCorrente che serve a mantenere l'ultima matricola usata

e un metodo statico privato che produce una nuova matricola. La classe ha anche un metodo statico

pubblico che permette di cambiare la matricola di uno studente.

public class Studente { // dichiarazione e inizializzazione di un campo statico private static long matricolaCorrente = 1000000; // metodo publico statico public static void cambiaMatricola(Studente s) { s.matricola = nuovaMatricola(); } private static long nuovaMatricola() { // metodo privato statico matricolaCorrente++; return matricolaCorrente; } private long matricola; // dichiarazione di campi (non statici) private String nome, cognome; public Studente(String nome, String cognome) { // costruttore matricola = nuovaMatricola(); this .nome = nome; this .cognome = cognome; } public String getNome() { return nome; } // metodi pubblici public String getCognome() { return cognome; } public long getMatricola() { return matricola; } public void stampa() { System.out.println("Matricola: " + matricola); System.out.println("Cognome: " + cognome + " Nome: " + nome); } }

Si osservi che il metodo statico cambiaMatricola() può accedere al campo privato dell'oggetto

Studente perché appartiene alla stessa classe. Nel costruttore è usata la parola chiave this che

rappresenta il riferimento all'oggetto stesso. Qui this è usato per potersi riferire ai campi nome e

cognome dell'oggetto che altrimenti sarebbero stati mascherati dagli omonimi argomenti del costruttore.

La suddetta classe è usata nel seguente programma.

public class Main { public static void main(String[] args) { // crea due oggetti di tipo Studente Studente stu1 = new Studente("Mario", "Rossi"); Studente stu2 = new Studente("Maria", "Verdi"); stu1.stampa(); // stampa i dati dei due studenti stu2.stampa(); // cambia la matricola del primo studente Studente.cambiaMatricola(stu1); // e la stampa System.out.println("Nuova matricola: " + stu1.getMatricola());

Si noti che i metodi (non statici), come ad esempio stampa(), possono essere invocati solamente in

relazione ad uno specifico oggetto, in questo caso gli oggetti stu1 e stu2 di tipo Studente. Mentre i

metodi statici, come cambiaMatricola(), possono essere invocati solamente in relazione alla classe,

proprio perché non appartengono ad alcun oggetto ma appartengono invece alla classe.

La prima classe

Consideriamo una classe, che chiameremo CharRect, i cui oggetti rappresentano rettangoli di caratteri

che possono essere visualizzati sulla console. Inizialmente la classe sarà molto spartana e permetterà di

rappresentare solamente rettangoli riempiti con il carattere '*'. Sarà via via raffinata ed ampliata

esemplificando nel contempo nuove caratteristiche del linguaggio Java e anche alcune tecniche di

progettazione.

La prima versione della classe permette di costruire un nuovo rettangolo fornendo la posizione del suo

carattere in alto a sinistra, la larghezza (numero di colonne) e l'altezza (numero di righe). La posizione è

data relativamente ad un ipotetico sistema di riferimento che numera le righe dall'alto verso il basso

partendo da 0 e le colonne da sinistra verso destra partendo sempre da 0. La classe ha un solo metodo il

quale stampa il rettangolo. Ecco una definizione di questa classe:

import static java.lang.System.; public class CharRect { private int left, top; // posizione del primo crattere in alto a sinistra private int width, height; // dimensioni del rettangolo // costruttore public CharRect( int l, int t, int w, int h) { left = l; top = t; width = w; height = h; } public void draw() { // stampa il rettangolo for ( int i = 0 ; i < top ; i++) out.println(); for ( int r = 0 ; r < height ; r++) { int right = left + width; for ( int c = 0 ; c < right ; c++) out.print(c < left? ' ' : ''); out.println(); } } }

La direttiva import static permette di "importare" i campi e i metodi statici di una classe.

Ovviamente, la classe va scritta in un file di nome CharRect.java. I campi sono tutti privati perchè

fanno parte dell'implementazione della classe e quindi non dovrebbero essere visibili dall'esterno.

Mentre, il costruttore e il metodo draw() devono essere pubblici per poter essere invocati liberamente

dall'esterno. Il costruttore inizializza i campi che definiscono l'oggetto rettangolo con i valori che saranno

forniti al momento della creazione. Quando, come in questo caso, è definito un metodo con almeno un

parametro, e non è esplicitamente definito il costruttore senza parametri, il costruttore di default non può

essere invocato. Vale a dire, non si può scrivere new CharRect().

Vediamo subito come questa classe può essere usata. Per fare ciò occorre una classe che implementa

un metodo main(). Definiamo quindi una classe che chiameremo Test (in un file di nome Test.java):

public class Test { public static void main(String[] args) { CharRect rectA = new CharRect(3, 0, 10, 5); CharRect rectB = new CharRect(6, 1, 12, 3);

Per l'implementazione conviene introdurre un metodo ausiliario che stampa una linea del rettangolo e che

può essere usato in entrambi i metodi di stampa. La nuova versione della classe è la seguente:

import static java.lang.System.; public class CharRect { private static final char DEF_FILLCHAR = ''; private static final char DEF_FILLCHAR2 = 'o'; private int left, top; private int width, height; private char fillChar = DEF_FILLCHAR, fillChar2 = DEF_FILLCHAR2; public CharRect( int l, int t, int w, int h) { left = l; top = t; width = w; height = h; } public void setChar( char c) { fillChar = c; } public void setChar( char c, char c2) { fillChar = c; fillChar2 = c2; } public void draw() { for ( int i = 0 ; i < top ; i++) out.println(); for ( int r = 0 ; r < height ; r++) drawLine(fillChar, fillChar); } public void drawVStripes() { for ( int i = 0 ; i < top ; i++) out.println(); for ( int r = 0 ; r < height ; r++) drawLine(fillChar, fillChar2); } // metodo ausiliario (privato) private void drawLine( char ch1, char ch2) { int right = left + width; for ( int k = 0 ; k < right ; k++) { char ch = ' '; if (k >= left) ch = ((k - left) % 2 == 0? ch1 : ch2); out.print(ch); } out.println(); } }

Il metodo drawLine() è privato perché è utile per implementare i metodi pubblici draw() e

drawVStripes() ma non deve essere accessibile dall'esterno della classe. Si osservi che, grazie

all'overloading, i due metodi setChar() hanno lo stesso nome. Un programma che mette alla prova la

nuova versione è il seguente:

public class Test { public static void main(String[] args) { CharRect rectA = new CharRect(3, 0, 10, 5); CharRect rectB = new CharRect(6, 1, 12, 3); CharRect rectC = new CharRect(10, 1, 4, 4); rectA.draw(); rectB.drawVStripes(); rectC.draw(); rectA.drawVStripes(); rectB.setChar('#', '!'); rectB.drawVStripes(); } }

Ed ecco il risultato:

oooooo oooooo oooooo





ooooo ooooo ooooo ooooo oooo*o #!#!#!#!#!#! #!#!#!#!#!#! #!#!#!#!#!#!

Questa è ancora una versione rudimentale della classe CharRect, più avanti vedremo delle versioni

molto più versatili e potenti.

Ed ora alcune considerazioni circa lo stile di programmazione che sono importanti perché se applicate

con costanza e coerenza migliorano la leggibilità del codice. Il nome di una classe di solito è un

sostantivo singolare che si riferisce direttamente all'oggetto della classe. Inoltre è consuetudine che i

nomi delle classi inizino con una maiuscola. Questo per meglio dstinguerli dagli altri identificatori (nomi

di metodi e variabili) che dovrebbero sempre iniziare con una minuscola. I nomi delle costanti invece,

come nel C, dovrebbero contenere solo maiuscole. I nomi dei metodi che semplicemente modificano i

valori dei campi della classe dovrebbero iniziare con set (come il metodo setChar()). Mentre quelli

che ritornano il valore di un campo dovrebbero iniziare con get (se ci fosse un simile metodo nella

nostra classe si chiamerebbe getChar()). Inutile, forse, aggiungere quanto sia importante per la

leggibilità, sopratutto per un linguaggio complesso come Java, una corretta e coerente indentazione del

codice. Per un lettore umano, può essere persino più importante della correttezza sintattica.

Esercizi

[Metodi_che_accedono] Aggiungere alla classe CharRect dei metodi per leggere i campi fillChar e

fillChar2 e inoltre aggiungere un metodo per modificare la posizione del rettangolo.

[Strisce_orizzontali] Aggiungere alla classe CharRect un metodo drawHStripes() che stampa il

rettangolo a strisce orizzontali come nell'esempio qui sotto:

oooooo


oooooo

L'implementazione può sfruttare il metodo drawLine()?

[Scacchiera] Aggiungere alla classe CharRect un metodo drawChessboard() che stampa il rettangolo

a mo' di scacchiera, come nell'esempio qui sotto:

oooo oooo oooo

Si può modifcare il metodo drawLine() in modo tale che risulti utile anche per stampare i rettangoli a