Java Tutorial - Parte 1 0.1
Un tutorial per esempi
Caricamento in corso...
Ricerca in corso...
Nessun risultato
Appendici

I principali comandi del Prompt di MS-DOS

I comandi del prompt del MS-DOS, o della Powershell di Windows possono essere visualizzati digitando il comando help sul prompt. Il sistema visualizzerà tutti i comandi disponibili ed una breve descrizione di essi.
Per avere maggiori informazioni sul singolo comando specificare il nome del comando dopo il comando help:

>help           // visualizza help generico
>help cd        // visualizza help del comando CD 

Il comando CD

Il oomando cd (change-directory) ci permette di cambiare la directory (=cartella) corrente. Accetta un unico argomento che è il path (=percorso) della cartella che sarà la nuova directory corrente. Il path può essere specificato in due modalità:

  • assoluto: si tratta del percorso assoluto che comincia con la lettera del drive
  • relativo: si tratta di un persorso relativo a partire dalla directory corrente: per "andare su" di un livello si usa il nome speciale ..\

Per esempio, i due comandi seguenti cambiano la directory corrente da C:\User\pippo in C:\Users\pluto

C:\Users\pippo>cd C:\User\pluto   // path assoluto 
C:\Users\pippo>cd ..\pluto        // path relativo 

Il comando DEL

Questo comando serve per cancellare uno o più files. Il comando accetta come argomento uno o più nomi di files da cancellare. Esempio:

>del file1 file2 file3

Il comando REN

Il comando ren (rename) serve per cambiare il nome di un file. Accetta come primo argomento il vecchio nome del file e come secondo argomento il nuovo nome del file. Per esempio, il seguente comando cambia il nome del file pippo in pluto.

>ren pippo pluto

Il comando COPY

Il comando copy (= copia) viene usato per creare una copia di un file. Accetta come primo argomento il nome del file da copiare e come secondo argomento il nome della copia, che non può essere uguale al nome del file sorgente a meno che la copia non venga eseguita in una cartella diversa. Per esempio, il seguente comando copia il file pippo nella sottocartella pluto mantenendo il nome originale:

>copy pippo pluto\

I principali comandi del terminale unix

I comandi della shell di Unix/Linux possono essere visualizzati digitando il comando help sul prompt. Il sistema visualizzerà tutti i comandi disponibili ed una breve descrizione di essi.
Per avere maggiori informazioni sul singolo comando specificare il nome del comando dopo il comando help:

$ help           // visualizza help generico
$ help cd        // visualizza help dul comando CD 

Il comando cd

Il oomando cd (change-directory) ci permette di cambiare la directory (=cartella) corrente. Accetta un unico argomento che è il path (=percorso) della cartella che sarà la nuova directory corrente. Il path può essere specificato in due modalità:

  • assoluto: si tratta del percorso assoluto che comincia con la lettera del drive
  • relativo: si tratta di un persorso relativo a partire dalla directory corrente: per "andare su" di un livello si usa il nome speciale ..\

Per esempio, i due comandi seguenti cambiano la directory corrente da /home/pippo in /home/pluto

[/home/pippo] $ cd /home/pluto   // path assoluto 
[/home/pippo] $ cd ../pluto      // path relativo 

Il comando rm

Il comando rm (=remove) serve per cancellare uno o più files. Il comando accetta come argomento uno o più nomi di files da cancellare. Esempio:

[/home/pippo] $ rm file file2 file3

Il comando mv

Il comando mv (move) serve per cambiare il nome di un file. Accetta come primo argomento il vecchio nome del file e come secondo argomento il nuovo nome del file. Per esempio, il seguente comando cambia il nome del file pippo in pluto.

[/home/pippo] $ mv pippo pluto

Il comando cp

Il comando cp (= copy) viene usato per creare una copia di un file. Accetta come primo argomento il nome del file da copiare e come secondo argomento il nome della copia, che non può essere uguale al nome del file sorgente a meno che la copia non venga eseguita in una cartella diversa. Per esempio, il seguente comando copia il file pippo nella sottocartella pluto mantenendo il nome originale:

[/home/pippo] $ cp pippo pluto/

Il complemento a due

Il complemento a due è una modalità di memorizzazione dei numeri interi utilizzata in tutti i computer fin dagli albori della informatica. Non solo consente un notevole risparmio di memoria ma semplifica i calcoli quando deveno essere eseguiti da una macchina.

Il complemento a dieci

Per comprendere il complemento a qualsiasi-base-numerica illustreremo lo stesso con il sistema al quale siamo abituati: il sistema decimale nel qual caso parliamo di complemento a dieci (e non a due, ma credo sia abbastanza assodato).
Il complemento a dieci si ottiene sottraendo il numero dallo ZERO e ignorando il riporto oltre il numero di cifre che si intende gestire. Per esempio, il complemento a dieci del numero 318 con il limite di cifre gestire di tre, si ottiene così:

                         0 0 0 -
   numero                3 1 8 =
                         -------
   complemento a 10      6 8 2

E' possibile usare questo complemento a dieci come un numero negativo: il suo valore è "meno 318".
Per esempio, supponiamo di dover sommare due numeri: il 416 ed il -318. Il calcolo è facile, per un cervello umano, abituato alla aritmetica decimale: il risultato è 98. Tuttavia, per un computer, la addizione di due numeri con segno diverso non è così semplice: innanzitutto è necessario analizzare il segno dei due operandi; uno è positivo, l'altro è negativo, quindi non si tratta di una addizione ma di una sottrazione. Successivamente è necessario analizzare il valore assoluto dei due numeri in modo da sottrarre il minore dal maggiore. Infine, il segno del risultato sarà uguale al segno del numero con valore assoluto maggiore.
In definitiva, quello che abbiamo effettivamente eseguito è:

   primo addendo   4 1 6 -
   secondo addendo 3 1 8  =
                  -------
   risultato         9 8

Tutte queste complicazioni non esistono nel complemento a dieci. Per eseguire la somma 416 + (-318) usiamo il complemento a dieci del secondo addendo ed eseguiamo una vera addizione:

   primo addendo                   4 1 6 +
   secondo addendo (compl. a 10)   6 8 2  =
                                 -------
   risultato                    (1)0 9 8 

Come possiamo osservare, però, dobbiamo ignorare il riporto che si genera oltre il numero di cifre che abbiamo deciso di gestire. Il numero di cifre gestite và deciso a priori e influenza il calcolo del complemento a 10. Se il numero di cifre gestite è 4, il complemento a 10 del numero 318 è:

                         0 0 0 0 -
   numero                  3 1 8 =
                         ---------
   complemento a 10      9 6 8 2

La domanda che viene spontanea è questa: se il valore 682 vale -318 come posso rappresentare il valore 682 quando il numero di cifre gestire è tre? La risposta è semplice: non posso!! Avendo deciso di gestire solo tre cifre, lo spazio dei valori numerici che và da ZERO a 999 (mille numeri) andrà diviso tra i due insiemi di numeri: quelli positivi e quelli negativi; quindi abbiamo 500 valori a testa:

  • i numeri da ZERO a 499 sono positivi
  • i numeri da 500 a 999 sono negativi, memorizzati in complemento a dieci

Per calcolare il valore assoluto di uno dei numeri negativi è sufficente ricalcolarne il complemento a dieci. Così, per esempio il valore 999 vale -1 poichè 0 - 999 = 1, se ignoriamo il riporto.

Proviamo ora il caso in cui una somma produca un risultato negativo, per esempio 274 + (-318) = -44. Anche in questo caso, non si tratta di una vera somma ma di una sottrazione visto che il segno dei due addendi è diverso. Ma noi usiamo la addizione avendo cura di calcolare il complemento a dieci del numero negativo:

   primo addendo                   2 7 4 +
   secondo addendo (compl. a 10)   6 8 2  =
                                 -------
   risultato                    (1)9 5 6 

Il risultato 956 è un numero negativo dal momento che si trova nel range tra 500 e 999. Il suo valore assoluto è 0 - 956 = 44.

Overflow nel complemento a dieci

Dal momento che per funzionare il complemento a dieci impone di ignorare i riporti dalla cifra più significativa, abbiamo comunque un problema quando viene superato il limite di capacità del insieme dei numeri positivi e negativi. Mi spiego meglio, cosa succede nella seguente operazione?

  499 + 1 = 500

In questo caso abbiamo un overflow cioè un trabocco poichè il risultato, interpretato come complemento a dieci non vale 500 come dovrebbe essere bensì -500!

Il complemento a due

I computer usano la tecnica del complemento a due per tutti gli interi, in qualsiasi linguaggio di programmazione, dal linguaggio macchina al più potente linguaggio di programmazione ad alto livello.
I dettagli implementativi vengono nascosti dal compilatore / interprete ma quello che avete imparato in questo capitolo si applica sempre a tutte le quantità numeriche gestite da un computer.

Il complemento a due funziona allo stesso modo di come abbiamo imparato funzioni il complemento a dieci ma, poichè ci sono solo due cifre a disposizione, i calcoli sono enormemente facilitati e, per una CPU, questo significa più velocità e meno complessità. Rispetto al complemento a dieci, il complemento a due ha queste caratteristiche che lo rendono ancora più efficiente:

  • calcolare il complemento a due è facilissimo: basta rigirare tutti i bit (gli "0" diventano "1" e viceversa) poi aggiungere uno (questo significa che non è necessario implementare una "sottrazione" come invece è accaduto nel complemento-a-dieci)
  • dividendo in due il range totale dei numeri a disposizione (come già visto con il decimale), il bit più significativo determina il segno: se "0" il numero è positivo, se "1" il numero è negativo.

Il metodo Object.equals

Come descritto in L'operatore di uguaglianza di Java l'operatore di uguaglianza in Java si comporta in modo diverso a seconda se le variabili coinvolte sono di tipo primitivo o sono degli oggetti.

In C e C++ è possibile sapere se due variabili si riferiscono allo stesso oggetto mediante l'operatore di uguaglianza tra puntatori ma in Jsva, non esistendo i puntatori, si è scelto di usare l'operatore di uguaglianza per determinare se una variabile si riferisce allo stesso identico oggetto in memoria:

String s1 = "cronos";
String s2 = s1;
boolean r = s1 == s2; // true

Vi è da osservare che l'operatore di uguaglianza ritorna true solo se le due variabili si riferiscono allo stesso oggetto indipendentemente se esse contengono lo valore o meno:

String s1 = "cronos";
String s2 = "cronos";
boolean r = s1 == s2; // false

Per determinare se un oggetto è "uguale" ad un altro nel senso che contenga o meno lo stesso valore si deve usare il metodo equals.

String s1 = "cronos";
String s2 = "cronos";
boolean r = s1.equals( s2 ); // true

La endianess

Un numero intero in Java ha diverse capienze, per esempio un int ha 32 bits mentre un short ne ha 16. Il tipo long è il più capiente con i suoi 64 bits. Tutti, comunque, devono essere scritti in memoria in due o più celle di memoria Sappiamo anche che ogni cella di memroia occupa 8 bits e che il tipo di dato che le rappresenta è il byte.

Prendiamo un numero di tipo short come per esempio il 1500, In binario viene scritto in due bytes come: 00000101 11011100 i cui valori decimali sono 5 (il byte più significativo) e 220 (il byte meno significativo). Tutte le celle di memoria vengono accedute dalla CPU tramite un indirizzo di memoria che parte da ZERO (la cella di memoria più bassa) fino alla dimensione del bus indirizzi.

Ebbene, vi sono due distinte architetture hardware per memorizzare i dati interi su più bytes:

  • quella che mette il byte meno significativo ad indirizzi di memoria più bassi e che si chiama little endian
  • quella che mette il byte meno significativo ad indirizzi di memoria più alti e che si chiama big endian
                memoria bassa                memoria alta
                 ----------------------   ---   -------
little-endian   | 220 |   5   |                        |
                 ----------------------   ---   -------

                ----------------------   ---   -------
big-endian     |   5 |  220  |                        |
                ----------------------   ---   -------

La modalità in cui le celle di memoria vengono memorizzate si chiama endianess che, a quanto ne so, non ha corrispondenza in italiano. Tutti i processori x86 usano la modalità little endian mentre la big endian viene usata da pochi processori come per esempio Spark.

Un nome alternativo alla endianess è il termine byte-order cioè l'ordine dei bytes. Di particolare rilevanza è in che modo i bytes vengono trasmessi sulle reti; ovvio che una connessione remota potrebbe interessare due macchine con architetture diverse.
Lo standard ha quindi definito il concetto di network byte order al quale tutte le implementazioni di rete devono attenersi: per la cronaca esso è uguale a big endian.

Quando usare il cast esplicito

Nella sezione Numeri abbastanza grandi del capitolo riguardante il programma di calcolo del MCD / MCM il lettore ha imparato che a volte è necessario usare un cast esplicito per ottenere un riusltato corretto.
in questa sezione approfondiremo il concetto di cast esplicito in modo che il lettore lo assimili totalmente; la mancata comprensione di questi concetti è alla base di bugs molto fastidiosi ed insidiosi.

Digitate e salvate il seguente spezzone di codice:

/*
dummy.java - test del cast esplicito
*/
class Dummy
{
public static void main( String[] args )
{
int alfa = 2;
int beta = 4;
int gamma = alfa / beta;
System.out.println( "Il risultato di 2 diviso 4 è: " + gamma );
}
}

E' ovvio che il risultato di 2 diviso 4 è 0.5 (zero-virgola-cinque) ma il risultato visualizzato dal programma è ZERO.
Ovvio, direte voi, la variabile gamma è dichiarata int e non potrà mai contenere un risultato frazionario. Daccordo, correggiamo l'errore:

float gamma = alfa / beta;

Ebbene, nulla è cambiato. Il riusltato visualizzato è sempre ZERO.
Il lettore deve sempre ricordare che il compilatore esegue le operazioni aritmetiche sempre e solo tra operandi dello stesso tipo e che il risultato è dello stesso tipo degli operandi.
Ne consegue che, poichè alfa e beta sono entrambi di tipo int, il compilatore esegue la divisione tra i due interi e che il risultato INTERO di detta operazione è ZERO.
Questo risultato (lo ZERO) viene poi assegnato ad una variabile di tipo float che conterrà pertanto il valore ZERO.

Per ovviare a questo comportamento non desiderato è necessario forzare il compilatore ad eseguire la operazione di divisione su due numeri di tipo float in modo che anche il risultato sia di tipo float.
Per questo usiamo il cast esplicito; non è nemmeno necessario convertire entgrambi gli operandi in float, ne basta uno solo:

float gamma = (float)alfa / beta;

Poichè dopo il cast esplicito i due operandi non sono più dello stesso tipo il compilatore è costretto a convertire anche beta in un float dal momento che le operazioni aritmetiche vengono eseguite su operandi dello stesso tipo.
Si dice in gergo che il compilatore ha eseguito una promozione della variabile beta in un tipo di dato che possiede una maggiore precisione.

Supponiamo ora di dover dividere una somma di denaro, poniamo 120,63 EURO in tre parti uguali ma che non siamo interessati ai centesimi di euro: vogliamo un risultato arrotondato ad un euro, quindi ad un intero:

/*
dummy2.java - test del cast esplicito
*/
class Dummy2
{
public static void main( String[] args )
{
float somma = 120.63;
int importo = somma / 3;
System.out.println( "Importo per persona è: " + importo );
}
}

Il compilatore Java si rifiuta di compilare questo spezzone di codice e segnala un errore.
Questo è dovuto al fatto che la operazione di divisione è eseguita su due dati di tipo diverso: somma è un float mentre la costante numerica 3 è un intero. Come scritto in precedenza, il compilatore promuove l'operando intero in un float ed assegna il risultato ad un float.
Il problema nasce quando il programmatore assegna un risultato di tipo float ad un intero: qui si potrebbe avere perdita di precisione ed il compilatore non lo permette. Per ovviare al problema si usa, ancora una volta, il cast esplicito per convertire il risultato della operazione da float a int:

int importo = (int) (somma / 3);

Il java debugger

TODO

La shell di Java

TODO

Crivello4: superare il limite

TODO

Indice Generale