1 Prezentarea Limbajului Java

 

 

1.1  Elementele de baza ale limbajului Java

 

1.1.1 Constante si variabile

 

            Constante

In limbajul Java exista constante : numerice, booleene, caracter si sir de caractere. Constantele numerice sunt intregi si reale.

 

Constantele intregi se pot reprezenta in bazele :

zece : 5, 3159, -76,

opt :  0235, -0741,  0614  (incep cu cifra 0),

saisprezece : 0x123,  0x2b5, 0xf123 (incep cu prefixul 0x).

Implicit, tipul constantelor intregi este int. Daca constanta este urmata de litera l sau L tipul va fi long.

 

Constantele reale : 1.2, 1.2f, 1.2d, 2., 1.1e5, 2.3e-6 . Sunt valori de tip double daca sunt urmate de litera d sau D si de tip float daca sunt urmate de litera f sau F. Daca nu se specifica explicit tipul, atunci se considera ca valoarea este de tip double.

 

Constantele booleene  : reprezinta valorile logice de adevarat si fals : true, false.

 

Constantele caracter : sunt caractere intre ghilimele simple, ex. ‘a’, ‘tn’ sau ca o notatie ‘tuxxx’, ex. ‘tu13f2’.

 

Constantele sir de caractere sunt siruri de caractere scrise intre doua duble ghilimele : : “acesta este un sir de caractere”, “tnsir de caractere precedat de ENTER”.

 

            Variabile

            Limbajul Java are trei tipuri de variabile : variabile instanta sau variabile c`mp si variabile locale. Primul tip va fi prezentat odata cu notiunile de clasa si obiect (Sectiunea 1.2.1). Variabilele locale sunt declarate in interiorul blocurilor de instructiuni (mai multe instructiuni Java cuprinse intre parantezele {}). O variabila locala este  vizibila doar in interiorul blocului in care a fost declarata. Declaratia unei variabile are sintaxa :

 

            <tip> <nume_variabila>a=valoare_initialas;

 

            double pi;

            sau

            double pi=3.1415;

 

            In declaratia unei variabile pot fi utilizati si modificatorii de acces, Acestia vor fi prezentati in Sectiunea 1.2.3. Totusi, facem precizarea ca, in cazul variabilelor locale, este permis doar modificatorul final.

           

 

 

            1.1.2 Operatori

            Operatorii aritmetici : +, -, *, /, % (modulo, doar pentru tipurile intregi) sunt binari. Operatorii + si - pot fi si operatori semn. Operatorul + este folosit si pentru concatenarea a doua siruri de caractere.

            Operatori de incrementare  ++ si decrementare -- sunt unari.

 

            int x=1;

            x++ Ű x=x+1 Ţ x=2; x-- Ű x=x-1 Ţ x=0

 

            Operatori logici : && (si), || (sau) care sunt binari si operatorul ! (negatie) care este unar. Au  operanzi booleeni.

            Operatori pe biti : & (si), | (sau), ^ (sau exclusiv) care sunt binari si  ~ (negatie) care este unar. Alti operatori pe biti sunt si >> (deplasare dreapata), <<  (deplasare  st`nga), >>> (deplasare dreapta fara extensie de semn). Au operanzi intregi.

            Operatorii relationali : >, >=, <, <=, == (egalitate), != (diferit). Rezultatul unei expresii relationale este de tip booleean.

            Operatori de atribuire combinati : +=, -=, *=, /=, &=, |=, ^=, %=, <<=, >>=, >>>=.

 

            float x,a;

            int i;

            x+=a Ű x=x+a; i<<=2 Ű i=i<<2

 

            Operatorul de decizie (?:) este ternar. O expresie cu acest operator are forma :

expresie1 ? expresie2: expresie3.

Expresie1 este de tip booleean. Daca aceasta este adevarata atunci intreaga expresie capata valoarea expresie2 altfel ia valoarea lui expresie3.

 

int min, a,b;

min=(a<b) ? a:b;

 

            Operatorul instanceof, care este binar, intoarce true daca obiectul din st`nga lui este o instanta a clasei sau  interfetei specificate in dreapta si false in caz contrar.

 

 

            1.1.3 Tipuri de date

            In limbajul Java exista doua categorii de tipuri de date : tipuri primitive si tipul referinta (cu adresa).

            Tipurile primitive sunt :

            · aritmetice :

· intregi : byte (1 octet), short (2 octeti), int (4 octeti), long (8 octeti) sunt intregi cu semn,

                        · reale : float (4 octeti), double (8 octeti),

            · caracter : char (2 octeti) asimilat cu intreg fara semn cu valori intre ‘tu000’ si ‘tufff’,

            · logic : boolean (1 octet) : true si false.

 

            Limbajul Java poseda pentru fiecare tip primitiv si c`te o clasa ale carei instante reprezinta obiecte similare cu valorile din tipul primitiv respectiv. Aceste clase sunt : Byte, Short, Integer, Long, Float, Double, Character, Boolean.          

Observatie : In limbajul Java caracterele sunt codificate pe 16 biti in codul Unicode. Setul Unicode este un superset al setului ASCII, fiecare caracter (octet) ASCII fiind completat, in acest caz, cu un octet semnificativ cu valoarea 0 (neinterpretat de masini care vad doar ASCII). Aceasta codificare se rasfr`nge, evident, si asupra sirurilor de caractere. Pentru sirurile de caractere  (care sunt privite ca tablouri de caractere reprezentate in Unicode) exista clasa String in care sunt descrise toate operatiile posibile cu siruri de caractere.

 

            Intre toate tipurile aritmetice se pot face conversii cu extensia zonei de memorie folosita pentru reprezentare sau micsorarea acesteia (caz in care pot apre truncheri ale valorilor), dupa caz. Tipul char poate fi convertit la unul dintre tipurile intregi sau reale. Limbajul admite si conversii de tip casting intre tipuri compatibile.  Prin casting se intelege atribuirea valorii unei expresii de un anumit tip catre o variabila de alt tip. Sintaxa este

 

            tip_data identificator=(tip_data)expresie;

 

            Iata c`teva exemple.

int i,j, a=10;

float x=5.77,y=90.3;

……

(double)(i+j) are tipul double,

 (int)(x-y) are tipul int.

            a=(int)y Ţ a=90 ; x=a Ţ x=10.0

 

            Modul de executie al operatiilor aritmetice este cel obisnuit. Se impune o observatie asupra operatorului de impartire. Rezultatul unei impartiri este intreg sau real dupa tipul operanzilor.

 

            int j;

            float x;

            j=1/2 Ţ j=0; j=6/3 Ţ j=2; j=9/4 Ţ j=2; x=9./2 Ţ x=4.5

 

Impartirea cu zero, in cazul operanzilor  de tip intreg, provoaca o exceptie de tip ArithmeticException. In cazul tipurilor reale impartirea cu zero nu provoaca o exceptie deoarece pentru aceste tipuri de date exista constantele : POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN (Not a Number) pe care le pot lua valorile expresiilor in astfel de cazuri.

            La evaluarea expresiilor cu operatori aritmetici binari se utilizeaza promovarea la tipul imediat superior in cazul operanzilor de tipuri numerice diferite. Astfel,

            · daca unul din operanzi este de tip double atunci celalalt este convertit la tipul double,

            · altfel daca unul din operanzi este de tip float celalalt este convertit la tipul float,

            · altfel daca unul dintre operanzi este de tip long celalalt este convertit la tipul long,

            · altfel ambii operanzi sunt convertiti la tipul int.

 

            Tipurile de date referinta sunt obiectele (instantele claselor) si tablourile. In Java, tipul referinta este singura modalitate de accesare a obiectelor. In cazul acestor tipuri, compilatorul pune adresa unui obiect sau tablou intr-o variabila. Astfel este posibil ca doua variabile diferite sa refere acelasi obiect. Spre deosebire de tipurile referinta, tipurile primitive sunt manevrate prin valoare. Pentru variabilele de tip referinta alocarea spatiului de memorie se face dinamic prin ordinul new. Dupa ce variabilele referinta nu mai sunt utilizate spatiul alocat lor este eliberat prin mecanismul “garbage collection”.

 

            Button a,b,c=new Button();

            int i,j;

            a=new Button();

            b=a;

            c.setLabel(“Start”);

            a.setLabel(“Gata”);

            String s1=a.getLabel(),s2=b.getLabel();

            String s3=c.getLabel();

            i=7; j=i; i=3;

 

            String-urile s1 si s2 au valoarea “Gata” pe c`nd s3 are valoarea “Start”. Variabilele i si j au valorile i=3, j=7.

 

1.1.3 Tablouri

            Principiile enuntate mai sus sunt valabile la tipul tablou deoarece acesta este un tip referinta. Sintaxa de declarare a unui tablou este

 

            tip_date nume_tablouas = new tip_dateanumar_elementes;

 

Crearea unui tablou se face in doua feluri :

1.      prin specificarea unei dimensiuni maxime :

byte bas=new bytea10s;

int aasas=new inta2sa3s;

float xasas=new floata3sas;

String myStringas=Stringa5s;

 

2.      prin initializare statica :

int tablouas={1,2,3,4,5,6};

 

Dupa cum se vede se pot declara si tablouri a caror elemente sun instante ale unor clase (cazul tabloului myString). Numarul elementelor unui tablou este precizat de c`mpul component length. Spre exemplu avem : b.length=10, aa1s.length=3.

 

            1.1.4 Instructiuni

            Marea majoritate a instructiunilor limbajului Java sunt preluate din limbajul C respect`nd sintaxa si semantica acestora. Deosebirea majora se face la instructiunile care necesita expresii conditionale. In Java tipul acestora este booleean spre deosebire de C unde orice valoare numerica diferita de zero este asociata cu true iar valoarea zero cu false. Vom exemplifica c`teva dintre principale instructiuni.

            Instructiunea de decizie if…else.

 

            if (test==true)

                        Instructiune 1;

                        // daca expresia test este adevarata

            else{

                        Instructiune 2;

                        ……

                        Instructiune k;

                        // daca test este falsa

            }

 

            Urmatorul cod exemplifica instructiunea de selectie switch. Foloseste intregul count pentru a realiza testul si implicit selectia.

 

            switch  (count){

                        case 1 : Instructiune 1;

                                        break;

                        case 2 : Instructiune 2;

                                        break;

                        case 3 : Instructiune 3;

                                        Instructiune 4;

                                        Instructiune 5;

                                        break;

                        default :Instructiune 6;

            }

           

            Exista trei instructiuni de ciclare. Instructiunea for este structurata astfel

 

            for(init; test; post_test)

                        Instructiune;

 

            Expresia init este una de initializare. Poate fi si o expresie complexa cu mai multe expresii despartite prin virgula (desi operatorul virgula nu exista in Java). Expresia test are tipul boolean; c`t timp acesta este adevarata se executa Instructiune (care poate fi si compusa). Evaluarea lui test se face inaintea executiei lui Instructiune.  Expresia post_test este cea in care se fac modificari asupra variabilelor ce dau valoarea expresiei test. Se fac, spre exemplu, incrementari sau/si decrementari.

 

            for(int k=0; k<10; k++)

               System.out.println(“Valoarea lui k este ”+k);

            ………

            int aas=new inta20s;

            int t;

            for(int i=0,j=19; i<j; i++,j--){

               t=aais;aais=aajs;aajs=t;

            }

 

            Instructiunea de ciclare while poseda doar partea de test din for.

 

            while (test)

               Instructiune;

 

            test se evalueaza inainte de executia lui Instructiune. Spre exemplu, o parte din codul de mai sus se poate rescrie cu while astfel

 

            int aas=new inta20s;

int i=0,j=19,t;

            while(i<j){

               t=aais;aais=aajs;aajs=t;

               i++;j--;

            }

 

            Instructiunea do…while executa testul la sf`rsit.

 

            do{

               Instructiune;

            }while (test);

 

            Un exemplu de folosire este urmatorul

 

            int aas=new inta20s;

            int r,t;

            do{

              r=0;

              for(int i=0; i<19; i++)

if (aais>aai+1s){

                           t=aais;aais=aai+1s;aai+1s=t;

                           r++;

                        }

            }while (r!=0);

 

            Au fost extinse fata de limbajul C instructiunile break si continue. Ele permit parasire, respectiv reiterarea celui mai interior ciclu dar pot avea etichete. Este deci posibila o secventa de forma

 

            test : if (…){

                            for…

                            ……

                            break test;

                            ……

                   }

 

In plus fata de limbajul C s-au introdus c`teva instructiuni. Astfel,

 

            sincronized (expresie) Instructiune;

 

            Ea asteapta accesul exclusiv la obiectul sau tabloul specificat prin expresie. In momentul c`nd a obtinutaccesul exclusiv, executa Instructiune (care poate fi si compusa). Cu acesta instructiune se executa sectiunile critice din contextele multithreading.

            Instructiunile package si import vor fi tratate mai t`rziu.

 

            1.1.5 Exceptii si manipularea lor

            O exceptie este un semnal prin care se indica faptul ca a aparut o situatie exceptionala : o eroare de intrare/iesire, o depasire de indice etc. Exceptiile sunt de doua feluri : exceptii propriu-zise si erori. Din prima categorie amintim : depasirile de indice, tentativa de citire dupa int`lnirea sf`rsitului de fisier, adresa host necunoscuta etc. Din categoria erorilor amintim : adresarea in afara memoriei, depasirea de stiva etc.

            Java defineste pentru erori si exceptii clasa java.lang.Throwable cu doua subclase java.lang.Exception si java.lang.Error. Pentru fiecare se instantiaza un obiect al uneia dintre cele doua subclase. Fiecare obiect contine, printre altele, un string ce are ca valoare mesajul explicativ al exceptiei respective.

            Manevrarea exceptiilor se face cu ajutorul instructiunilor try, catch, finally.

 

            try{

                        Informeaza ca in acest bloc este posibila aparitia unor situatii neobisnuite.

            }

            catch (Exceptie_1 E1){

Trateaza aparitia Exceptie_1. Obiectul E1 este generat de catre aceasta exceptie si este utilizat pentru tratarea situatiei.

            }

            ………

            catch (Exceptie_n En){

                        Trateaza Exceptie_n.

            }

            finally{

Acest bloc se executa intotdeauna, indiferent de modul de parasire a blocului try. Parasirea se poate face :

-         normal, la atingerea sf`rsitului de bloc try,

-         datorita unei instructiuni break, continue sau return aparuta in blocul try,

-         datorita unei exceptii tratata prin catch,

-         datorita unei exceptii netratate prin catch.

            }

 

            Java cere ca orice metoda care ar putea provoca aparitia unei exceptii fie sa trateze aceasta exceptie prin try si catch fie sa o anunte printr-o declaratie throws in prototip ca in exemplul de mai jos.

 

            public void metoda1() throws IOException{

            ………

            }

            public void metoda2() throws Exceptie_1, Exceptie_2 {

            ………

            }

 

1.2  Orientarea pe obiecte a limbajului Java

 

            1.2.1 Clase si obiecte

            Un program Java este format din una sau mai multe clase. O clasa este sablonul care descrie obiecte de un anumit tip, preciz`nd structura si functionalitatea acestora. Declararea unei clase este similara cu declararea unui nou tip de date. Obiectele se mai numesc si instante ale unei clase deoarece sunt variabile referinta declarate de tipul unei clase. Practic, un obiect apartin`nd unei clase este o materializare a entitatii descrisa de clasa.

            O clasa consta din variabile membre si functii membre sau metode. Sintaxa de definire a unei clase este

 

class nume_clasa aextendsnume_supraclasas aimplements nume_interfatas{

                adeclaratii_variabile;s

                adeclaratii_metode;s

}

 

Iata un prim exemplu,

 

Exemplul 1

import java.io.*;

public class Ex1

{

            public int val=0;

            public void setVal(int a){

                        val=a;

            }

            public int getVal(){

                        return val;

            }

            public int getHalf(){

                        int h=val/2;

                        return h;

            }

            public static void main (Stringas args)

            {

                        Ex1 e=new Ex1();

                        System.out.println("Valoarea variabilei este "+e.val);

                        e.setVal(15);

                        System.out.println("Valoarea variabilei este "+e.getVal());

                        System.out.println("Semivaloarea variabilei este "+e.getHalf());

            }

}

 

            Acest exemplu (program) consta dintr-o singura clasa iar aceasta consta dintr-o singura variabila, val si trei metode setVal(),getVal(),getHalf(). Sa observam prezenta a inca unei metode, anume metoda main() al carei rol il vom evidentia mai jos. Variabila val este o variabila instanta sau variabila c`mp (definita in interiorul clasei). Ea corespunde unei instante a clasei, in cazul de fata obiectul e. Capata loc in memorie si dispare odata cu obiectul. Variabila h din interiorul metodei getHalf() este o variabila locala.

            Metodele clasei se definesc dupa sintaxa

 

            <tip_returnat> nume_functie(a<lista_parametri>s){

                        adeclaratii_locale;s

                        lista_instructiuni;

            }

 

            Sa notam ca in linia de declaratie a metodei se poate folosi si unul dintre modificatorii de acces (Sectiunea 1.2.3) pozitionat inaintea tipului returnat. Daca metoda nu returneaza nicio valoare (cazul procedurilor din alte limbaje) se foloseste cuv`ntul cheie void. Tipul metodei precum si tipul, numarul si ordinea parametrilor poarta numele de “semnatura metodei” (sau prototipul ei). Metodele clasei sunt apelate doar prin intermediul unui obiect, aici obiectul e.  Exista si o exceptie de la aceasta regula, excepte  care va fi prezentata ulterior.

Instantele claselor se declara ca variabile referinta; spatiul de memorie necesar unei instante de clasa se rezerva printr-o declaratie cu cuv`ntul cheie new. O declaratie simpla

 

            Ex1 e1;

 

creaza o variabila care poate contine o referinta la obiecte de tip Ex1, fara sa creeze un obiect instanta pentru clasa Ex1.

            Punctul de intrare in orice aplicatie Java este metoda main() care trebuie sa se gaseasca in una (si numai una) dintre clasele aplicatiei. Compilatorul Java cauta aceasta metoda si executa instructiunile din cadrul acesteia. Prototipul (amprenta) functiei main() este tot timpul acelasi, anume cel precizat in exemplul de mai sus.

 

            1.2.2 Constructori

            In Exemplul  1 se observa ca variabila membra val a obiectului e are initial valoarea 0, corespunzatoare declaratiei din cadrul clasei. Valoarea acestei variabile poate fi modificata prin metoda setVal(). Limbajul Java permite o alta modalitate de initializare a variabilelor membre ale unui obiect inca din momentul rezervarii spatiului in memorie. Aceasta se realizeaza prin intermediul constructorilor. Acestia sunt metode speciale ale unei clase care sunt apelate implicit in momentul instantierii unui obiect al clasei. Constructorii au sintaxa obisnuita a unei metode cu deosebirea ca nu au tip iar numele lor este acelasi cu cel al clasei.

 

Exemplul 2

import java.io.*;

public class Persoana

{

            private String nume;

            private int virsta;

            public Persoana(){

              nume=”fara nume”;

              virsta=0;

            }

            public Persoana(String nume, int virsta){

              this.nume=nume;

              this.virsta=virsta;

            }

            public String getNume(){

              return nume;

            }

            public int getVirsta(){

              return virsta;

            }

            public void list(){

             System.out.println(this.getNume()+” are virsta “+this.getVirsta());

            }

            public static void main (Stringas args)

            {

                        Persoana pers1,pers2;

                        pers1=new Persoana();

                        pers2=new Persoana(“Ionescu”,35);

                        pers1.list();

                        System.out.print("A doua persoana este "+pers2.getNume());

                        System.out.println(“ si are virsta ”+pers2.getVirsta());

            }

}

 

            Datorita facilitatilor de suprascriere a metodelor (Sectiunea 1.2.4), in cadrul aceleiasi clase se pot scrie mai multi constructori care se deosebesc prin semnatura. In exemplul de mai sus, la instantierea obiectului pers1 se apeleaza primul constructor astfel ca prima persoana nu va avea nume iar v`rsata va fi 0. La instantierea lui pers2 se apeleaza al doilea constructor deci, a doua persoana va avea numele “Ionescu ” si v`rsta 35.

            In cadrul celui de-al doilea constructor se observa utilizarea identificatorului this. Acesta specifica obiectul curent. La instantierea obiectului pers2, this il va indica pe acesta, deci c`mpurile this.nume si this.virsta vor fi ale lui pers2. Prin intermediul lui this se poate face distinctia intre parametrii metodei si c`mpurile variabile ale obiectului, in cazul c`nd au acelasi nume.

            Prin intermediul lui this se poat apela metodele claseu, inclusiv constructorii. Astfel, primul constructor al clasei Persoana poate fi rescris utiliz`nd pe cel de-al doilea.

 

public Persoana(){

              this(“fara nume”,0);

            }

 

            In cazul lipsei constructorilor intr-o clasa (vezi Exemplul 1) se apeleaza constructorul supraclasei (Sectiunea 1.2.5).

 

            1.2.3 Modificatori de acces

            Java foloseste modificatorii de acces pentru a specifica nivelul de vizibilitate al variabilelor si metodelor in raport cu alte clase. Java are patru nivele de acces : public, private, protected si package. Ultimul necesita o prezentare separata.

            Modificatorul public indica faptul ca variabila sau metoda este vizibila din interiorul oricarei alte clase sau metode. Este cazul variabilei c`mp val din clasa Ex1 (Exemplul 1). Variabila val a obiectului e din functia main() poate fi accesata sub forma e.val.  Daca am adauga aplicatiei din Exemplul 1 inca o clasa,

 

public class Clasa2{

………

public void metoda(){

   Ex1 e1=new Ex1();

   e1.val=50;

}

………

}

 

am putea accesa c`mpul val al obiectului e1 instantiat intr-o metoda a acestei noi clase.

            Modificatorul public trebuie asociat oricarei metode care se doreste a fi accesata in orice punct al aplicatiei. Evident, constructorii trebuie declarati cu acest modificator (vezi Exemplul 2).

            Modificatorul private indica faptul ca variabila sau metoda pentru care este folosit nu poate fi accesata din interiorul oricarei alte clase cu exceptia clasei in care este declarata acea variabila sau metoda. Este cazul variabilelor c`mp nume si virsta din clasa Persoana. Ele nu pot fi vazute din exteriorul acestei clase. Daca am aduga aplicatiei din Exemplul 2 clasa,

 

public class Clasa2{

………

public void metoda(){

   Persoana p=new Persoana();

   p.nume=”Popescu”;

}

………

}

 

am constata ca instructiunea p.nume=”Popescu” furnizeaza eroare de compilare deoarece variabila nume este inaccesibila din exteriorul clasei Persoana.

            Modificatorul protected restrictioneaza accesul la subclasele clasei in care acest modificator insoteste declaratii de variabile sau metode (a se vedea sectiunea despre mostenire). Mai precis, acele variabile sau metode declarate cu protected pot fi accesate direct doar in subclasele clasei in care sunt membre.

            Ultimul modificator, package, nu este practic un modificator de acces.  El specifica o biblioteca (pachet) de clase. Daca nu se specifica niciun modificator pentru o variabila sau metoda atunci aceasta este vizibila in toate clasele din aceeasi biblioteca.

 

            1.2.4 Suprascrierea metodelor

            In Sectiunea 1.2.1 am vazut ca fiecare metoda este caracterizata de semnatura ei. Semnatura impreuna cu numele identifica unic o metoda. Acest aspect este important deoarece Java poseda un mecanism prin intermediul caruia se pot scrie intr-o clasa mai multe metode cu acelasi nume dar cu semnaturi diferite. Acest mecanism poarta numele de suprascrierea metodelor si este foarte des int`lnit in procesul de mostenire.

 

Exemplul 3

import java.io.*;

public class Dreptunghi

{

            private float latime=1;

            private float inaltime=1;

            public void setDim(float a){

              latime=a;

            }

            public void setDim(float a,float b)

              latime=a;inaltime=b;

            }

            public float getPerimetru(){

              return 2*(latime+inaltime);

            }

            public float getArie(){

              return latime*inaltime;

            }

            public void list(){

             System.out.print(“dreptunghi cu latimea “+latime+” si inaltimea “+inaltime);

            }

            public static void main (Stringas args)

            {

                        Dreptunghi d1=new Dreptunghi();

                        d1.setDim(25);

                        d1.list();

                        System.out.println(“are perimetrul “+d1.getPerimetru()+” si aria “+d1.getArie());

                        Dreptunghi d2=new Dreptunghi();

                        d2.setDim(25,37);

                        d2.list();

                        System.out.println(“are perimetrul “+d2.getPerimetru()+” si aria “+d2.getArie());

            }

}

 

            In clasa Dreptunghi exista doua versiuni ale metodei setDim() cu unul si respectiv doi parametri.

 

            1.2.5 Extinderi de clase, clase componente, mostenire

            Java permite doua mecanisme de folosire a facilitatilor unei clase in interiorul alteia : clasa componenta si clasa care extinde (mosteneste) alta clasa.

            O clasa componenta apare atunci c`nd in interiorul unei clase se foloseste o variabila de tipul altei clase.

 

Exemplul 4

import java.io.*;

public class Salariat

{

            private Persoana pers;

            private int sectie;

            private float salariu;

            public Salariat(){

              pers=new Persoana();

              sectie=0;

  salariu=0;

            }

            public Persoana(String nume, int virsta, int sectie, float salariu){

              pers=new Persoana(nume,virsta);

              this.sectie=sectie;

              this.salariu=salariu;

            }

            public String getNume(){

              return pers.getNume();

            }

            public int getVirsta(){

              return pers.getVirsta();

            }

            public int getSectie(){

              return sectie;

}

public float getSalariu(){

  return salariu;

}

            public void list(){

             System.out.print(pers.getNume()+” are virsta “+pers.getVirsta());

             System.out.println(“lucreaza la sectia “+sectie+” si are salariul “+salariu);

            }

            public static void main (Stringas args)

            {

                        Salariat salariat1,salariat2;

                        salariat1=new Salariat();

                        pers2=new Salariat(“Ionescu”,35,1,2500000);

                        salariat1.list();

                        salariat2.list();

            }

}

 

            Clasa Persoana este clasa componenta in clasa Salariat. Desi este int`lnita in programe, facilitatea clasei componente are unele inconveniente. Metodele getNume() si getVirsta() folosesc serviciile cu acelasi nume din clasa Persoana prin intermediul instantei pers. Aceast aspect pare cam fortat desi nu este un inconvenient. Situatia se schimba atunci c`nd observam cazul primului salariat care se instantiaza prin intermediul constructorului fara parametri. In interiorul acestuia se instantiaza variabila pers prin constructorul fara parametri al clasei Persoana. Datorita limitarilor functionale din clasa Persoana nu vom putea seta numele si v`rsta acestui salariat cu alte valori. Vor ram`ne cele implicit stabilite in clasa Persoana.

            In cazul celor doua tipuri de date Persoana si Salariat solutia eleganta este oferita de mecanismul de extindere de clasa. Folosind cuv`ntul cheie extends vom defini clasa Salariat ca extensie a clasei Persoana. Spunem ca Salariat este subclasa a clasei Persoana sau ca aceasta este supraclasa a clasei Salariat. Noua clasa va mosteni toti membrii clasei Persoana. Pentru a avea acces nemijlocit in clasa Salariat asupra variabilelor mostenite nume si virsta este necesar sa schimbam in clasa Persoana modificatorul de acces al acestora din private in protected. Cu aceasta precizare, clasa Salariat arata astfel,

 

Exemplul 5

import java.io.*;

public class Salariat

{

            private int sectie;

            private float salariu;

            public Salariat(){

              nume=”fara nume”;

              virsta=0;

              sectie=0;

  salariu=0;

            }

            public Persoana(String nume, int virsta, int sectie, float salariu){

              super(nume,virsta);

              this.sectie=sectie;

              this.salariu=salariu;

            }

            public String getNume(){

              return super.getNume();

            }

            public int getVirsta(){

              return super.getVirsta();

            }

            public int getSectie(){

              return sectie;

}

public float getSalariu(){

  return salariu;

}

public void setNume(String nume){

  this.nume=nume;

}

public void setVirsta(int virsta){

  this.virsta=virsta;

}

public void setSectie(int sectie){

  this.sectie=sectie;

}

public void setSalariu(float salariu){

  this.salariu=salariu;

}

            public void list(){

             super.list();

             System.out.println(“lucreaza la sectia “+sectie+” si are salariul “+salariu);

            }

            public static void main (Stringas args)

            {

                        Salariat salariat1,salariat2;

                        salariat1=new Salariat();

                        salariat2=new Salariat(“Ionescu”,35,1,2500000);

                        salariat1.list();

                        salariat1.setNume(“Popescu”);

                        salariat1.setVirsta(23);

                        salariat1.setSectie(2);

                        salariat1.setSalariu(1300000);

                        salariat1.list();

                        salariat2.list();

            }

}

 

            Se observa ca o parte dintre instructiunile primului constructor Salariat() coincid cu instructiunile primului constructor Persoana(). Pentru a evita astfel de duplicari, Java permite apelul constructorului supraclasei in cadrul constructorului subclasei folosind cuv`ntul cheie super, dupa cum se vede in al doilea constructor Salariat(). Singura restrictie este ca super sa fie  primul apel in cadrul noului constructor.

            Metodele getNume() si getVirsta()  suprascriu pe cele din clasa Persoana prin simplul apel al acestora folosind super.  Metoda list() o suprascrie pe cea din clasa Persoana dar adauga si o noua instructiune.

            In Java este permisa mostenirea multipla, spre exemplu clasa C mosteneste (extinde) clasa B care mosteneste clasa A. Fie x un nume de variabila int`lnit in fiecare clasa. Atunci, in clasa C sunt posibile referirile,

 

this.x            refera x din C

            super.x refera x din B

            ((B)this).x refera x din B

            ((A)this).x refera x din A

 

            Daca o clasa este declarata cu atributul final atunci ea nu mai poate fi extinsa. Opusul acestui atribut este abstract.

 

1.2.6 Variabile si metode statice

            P`na acum, am vazut ca fiecare instanta a unei clase contine c`te un exemplar din fiecare variabila precizata in definitia clasei. Limbajul Java permite existenta unor variabile globale unei clase, adica variabile a caror valoare este comuna tuturor instantelor clasei si poate fi referita de catre acestea. Aceste variabile se numesc variabile statice si se declara cu modificatorul static. O variabila statica este locala clasei si se creaza la incarcarea clasei. }i metodele se pot declara cu atmodificatorul static. O metoda statica nu poate folosi dec`t variabile statice si nu poate apela dec`t alte metode statice dar poate fi apelata din orice alta metoda a clasei. Variabilele si metodele statice pot fi referite folosind numele clasei deoarece ele sunt asociate clasei si nu instantelor clasei.

 

Exemplul 6

import java.io.*;

public class Floare

{

            private int nrPetale;

            private String nume;

            private boolean miros;

            static int numarObiecteFloare=0;

            static void incNrOb(){

                        numarObiecteFloare++;

            }

            static int getNrOb(){

                        return numarObiecteFloare;

            }

            public Floare(String nume, int nrPetale, boolean miros){

                        this.nume=nume;

                        this.nrPetale=nrPetale;

                        this.miros=miros;

                        incNrOb();

            }

            public void list(){

                        System.out.print(nume+" are "+nrPetale+" petale si ");

                        if (!miros) System.out.print(" nu ");

                        System.out.println("are miros");

            }

            public static void main (Stringas args)

            {

                        System.out.println("Numar de flori "+Floare.getNrOb());

                        Floare f1=new Floare("Ghiocel", 4,false);

                        Floare f2=new Floare("Trandafir",20,true);

                        Floare f3=new Floare("Crin", 10,true);

                        f1.list();

                        f2.list();

                        f3.list();

                        System.out.println("Sunt "+Floare.getNrOb()+" flori");

            }

}

 

1.2.7 Clase abstracte si interfete

            In limbajul Java se pot preciza modele de functionare care urmeaza sa fie implementate. Acestea se introduc prin intermediul claselor abstracte si a interfetelor.

O clasa abstracta este o supraclasa pentru o ierarhie de clase. Ea contine variabile si metode obisnuite carora li se specifica semnatura. Unele metode pot fi chiar implementate in clasa abstracta. Acestea definesc functionarea comuna a claselor care formeaza ierarhia. Alte metode, precizate doar ca prototip, urmeaza sa fie obligatoriu implementate in subclase. Ele sunt insotite de  atributul abstract. O clasa este considerata abstracta daca contine macar o metoda abstracta. De aceea nu este neaparat obligatoriu ca ea sa fie declarata cu abstract. In clasele care extind clasa abstracta se pot defini sI noi membrii (variabile sau metode) care nu au fost precizati in clasa abstracta.

In exemplul care urmeaza definim clasa abstracta Poligon. In cadrul ei se regasesc cele amintite mai sus.

 

Exemplul 7

abstract class Poligon{

            protected int nrLaturi=0;

            protected float latas;

            protected void setNrLaturi(int n){

                        nrLaturi=n;

                        lat=new floatanrLaturis;

            }

            protected void setLaturi(float aas){

                        for(int i=0;i<nrLaturi;i++){

                                    latais=0;

                                    try{

                                                latais=aais;

                                    }

                                    catch(Exception e){}

                        }

            }

            abstract void list();

            abstract float perimetru();

}

            Clasa contine variabila membra nrLaturi, tablul Lat care va contine lungimile laturilor, metodele setNrLaturi() si setLaturi() care au functionalitate comuna pentru intreaga ierarhie sI metodele abstracte list() si perimetru(). Din aceasta clasa extindem clasele Patrat si Dreptunghi.

 

public class Patrat extends Poligon

{

            public Patrat(float aas){

                        setNrLaturi(1);

                        setLaturi(a);

            }

            public void list(){

                        System.out.println("Este un patrat cu latura "+lata0s);

            }

            public float perimetru(){

                        return 4*lata0s;

            }

}

public class Dreptunghi extends Poligon

{

            public Dreptunghi(float aas){

                        setNrLaturi(2);

                        setLaturi(a);

            }

            public void list(){

                        System.out.println("Este un dreptunghi cu laturile "+lata0s+" "+lata1s);

            }

            public float perimetru(){

                        return 2*(lata0s+lata1s);

            }

}

 

            }i acum sa folosim ierarhia definita.

 

public class Prelucrare

{

            public static void main (Stringas args)

            {

                        Poligon plg;

                        float aas={1.2f,3.4f};

                        Dreptunghi d=new Dreptunghi(a);

                        d.list();

                        plg=d;

                        System.out.println("Perimetrul este "+plg.perimetru());

                        float bas={5.f};

                        Patrat p=new Patrat(b);

                        p.list();

                        plg=p;

                        System.out.println("Perimetrul este "+plg.perimetru());

            }

}

 

 

Clasa abstracta Poligon nu poate fi direct instantiata. O variabila de tipul ei poate prelua valoarea unei instante a uneia dintre subclase.

O interfata se precizeaza cu cuv`ntul cheie interface si defineste un tip. Ea este un model pe care orice clasa care il implementeaza trebuie sa-l respecte intocmai. Metodele care apar intr-o interfata sunt implicit publice iar variabilele (daca sunt declarate) sunt constante chiar daca nu se specifica aceasta in mod explicit. Se pot crea ierarhii de interfete la fel ca in cazul claselor.

Ca si in cazul claselor abstracte se pot declara variabile de tipul unei interfete. Valoarea unei astfel de variabile poate fi orice referinta la un obiect care apartine unei clase ce implementeaza interfata.  Variabilele de tip interfata abstractizeaza notiunea de obiect deoarece sunt independente de clasele care fac implementarea. Reluam exemplul anterior.

 

Exemplul 8

import java.io.*;

interface Poligon{

            void setLaturi(float aas);

            void list();

            float perimetru();

}

class Patrat implements Poligon

{

            private float Lat;

            public void setLaturi(float aas){

                                    try{

                                                Lat=aa0s;

                                    }

                                    catch(Exception e){Lat=0.0f;}

            }

            public Patrat(float aas){

                        setLaturi(a);

            }

            public void list(){

                        System.out.println("Este un patrat cu latura "+Lat);

            }

            public float perimetru(){

                        return 4*Lat;

            }

}

class Dreptunghi implements Poligon

{

            private float Latas;

            public void setLaturi(float aas){

                        for(int i=0;i<2;i++)

                                    try{

                                                Latais=aais;

                                    }

                                    catch(Exception e){Latais=0.0f;}

            }

            public Dreptunghi(float aas){

                        Lat=new floata2s;

                        setLaturi(a);

            }

            public void list(){

                        System.out.println("Este un dreptunghi cu laturile "+Lata0s+" si "+Lata1s);

            }

            public float perimetru(){

                        return 2*(Lata0s+Lata1s);

            }

}

public class Prelucrare

{

            public static void main (Stringas args)

            {

                        Poligon plg;

                        float aas={1.45f};

                        Patrat            p=new Patrat(a);

                        p.list();

                        plg=p;

                        System.out.println(" cu perimetrul "+plg.perimetru());

                        float bas={2.3f, 7.8f};

                        Dreptunghi d=new Dreptunghi(b);

                        d.list();

                        plg=d;

                        System.out.println(" cu perimetrul "+plg.perimetru());

            }

}

 

            1.2.8 Tratarea exceptiilor

 

            In limbajul Java, o exceptie este un eveniment care nu permite continuarea normala a executiei programului. Tratarea erorilor de executie prin exceptii este o caracteristica a limbajului Java. Utilizarea exceptiilor este mecanismul de a renunta la executia secventei de cod care a provocat eroarea, semnalarea acesteia si eventual, executia unei secvente de tratare corespunzatoare.

            Tratarea unei erori are doua componente : o componenta, throws, care semnaleaza exceptia si alta care o trateaza efectiv. Secventa de tratare poate fi in aceeasi metoda in care s-a semnalat exceptia, in metoda care a apelat metoda respectiva sau intr-o metoda dintr-un sir mai lung de apeluri.

            Practic, o exceptie este un obiect care reprezinta o instantiere a unei clase obtinuta prin extinderea clasei Throwable sau a unei subclase a acesteia. Din Throwable sunt dezvoltate doua ierarhii Error si Exception. Prima reprezinta exceptii generate de erori de functionare a mediului Java. Aceste obiecte nu pot fi instantiate in program. Clasa Exception sta la baza a doua ierarhii. Una, RunTimeException se refera la greseli de programare (ex. depasirea limitelor unui tablou) si o alta care poate fi creata de catre programator.

            Obiectul generat in cazul unei exceptii este utilizat pentru a transmite informatii de la secventa de cod care a produs eroarea la secventa de cod care o trateaza.

            Semnalarea exceptiei se face cu instructiunea

 

            Throw identif_obiect;

 

fara sa se faca si tratarea ei. Argumentul instructiunii este un obiect din ierarhia Exception. Tratarea exceptiilor se face cu instructiunea try catch. Ea indica secventa de cod in care poate sa apara o exceptie, tipul acesteia si modul in care se face tratarea ei. Cuv`ntul cheie throws poate fi utilizat si in definitia unei metode in corpul careia poate sa apara o exceptie fara ca aceasta sa fie tratata in metoda.

 

 

public class TestException extends Exception

{

            TestException(){super();}

            TestException(String s){super(s);}

}

 

import java.io.*;

public class TestThrow

{

            static int thrower(String s) throws TestException {

                        try{

                                    if (s.equals("divide")){int i=0;return i/i;}

                                    if (s.equals("null")){s=null;return s.length ();}

                                    if (s.equals("test")) throw new TestException("Mesaj Test");

                                    return 0;

                        }finally{

                                    System.out.println("athrower("+s+") genereazas");

                        }

            }

            public static void main (Stringas args)

            {

                        for(int i=0;i<args.length;i++)  

                                    try{

                                                thrower(argsais);

                                                System.out.println("Test "+argsais+" fara exceptie");

                                    }

                                    catch(Exception e){

                                                System.out.println("Test "+argsais+" exceptie de clasa "+e.getClass()+" cu mesajul : "+e.getMessage());

                                    }

            }

}

 

            Exemplul declara o clasa TestException care extinde clasa Exception. In metoda main a clasei Test se genereaza spre testare trei exceptii prevazute in limbajul Java si exceptia TestException.

 

1.3  Biblioteci (packages) in Java

 

Un program Java este o colectie de declaratii de clase care sunt organizate in biblioteci (packages). Membrii unei biblioteci sunt deci, clase sau interfete declarate intr-o unitate de compilare comuna. Termenul de package desemneaza nu numai o biblioteca, el reprezinta si directorul care contine toate fisierele utilizate pentru declararea claselor ce formeaza biblioteca.

Declararea unei biblioteci se face prin ordinul

 

            package nume;

 

unde nume reprezinta calea completa a directorului unde se afla biblioteca. Spre exemplu

 

            package dir1.aaa.my_package;

 

declara biblioteca my_package aflata in directorul dir1/aaa/my_package. Declaratiile de import sunt sub doua forme

 

            import nume_complet_biblioteca;

            import nume_biblioteca.*;

 

spre exemplu

 

            import java.util.Vector;

            import java.util.*;

 

            In concluzie, o unitate de compilare este formata din : declaratii package, declaratii de import si declaratii de clase. O unitate de compilare poate contine doar o singura clasa declarata public. Daca aceasta exista, atunci numele fisierului care o contine trebuie sa fie format din numele clasei si extensia “java”. In urma compilarii rezulta c`te un fisier cu extensia class pentru fiecare clasa din pachet si numele acelasi cu numele clasei corespunzatoare.

            Limbajul Java se bazeaza pe o serie de biblioteci cu ajutorul carora se realizeaza aplicatiile. Principalele biblioteci sunt :

-         java.lang contine clasele de cea mai larga utilizare in limbaj;

-         java.util contine clase care implementeaza, in general, structuri de date utile;

-         java.io contine clasele ce implementeaza fuctii de I/O cu fisiere;

-         java.applet contine clase ce implementeaza functii pentru applet-uri;

-         java.net contine clase destinate lucrului in retea.

 

1.3.1 Pachetul java.lang

 

            O parte dintre clasele acestui pachet sunt : Error, Exception; o serie de clase care implementeaza exceptii precum ArithmeticException, ArrayIndexOutOfBoundsException, ArrayStoreException; clase corespunzatoare tipurilor de date de baza : Boolean, Byte, Character, Short, Integer, Long, Float, Double; clase de lucru cu siruri de caractere : String, StringBuffer; clasa Math care contine functiile matematice; clasele System, Thread, Throwable. Toate clasele extind clasa Object membra tot a pachetului java.lang.

            Toate clasele care implementeaza tipurile de date de baza poseda functii de conversie cu sirurile de caractere (String-uri) in ambele directii. Astfel, poseda functii valueOf (clasele Float, Double, Long) sau Byte.parseByte(), Integer.parseInt(), Long.parseLong() care transforma un sir de caractere in valoarea numerica corespunzatoare. Toate aceste clase poseda o metoda toString care transforma o valoare din tipurile precizate in sir de caractere.

            Urmatorul program exemplifica conversii cu c`teva dintre tipurile amintite.

 

import java.io.*;

public class ShowConvers

{

            public static void main (Stringas args)

            {

                        String S=new String();

                        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

                        long l;

                        int i;

                        Double d;

                        try{

                                    System.out.println("Introduceti un intreg : ");

                                    S=br.readLine();

                                    i=Integer.parseInt(S);

                                    System.out.println("Ati introdus numarul "+i);

                                    System.out.println("Introduceti un intreg lung : ");

                                    S=br.readLine();

                                    l=Integer.parseInt(S);

                                    System.out.println("Ati introdus numarul "+l);

                                    System.out.println("Introduceti un numar real : ");

                                    S=br.readLine();

                                    d=Double.valueOf(S);

                                    System.out.println("Ati introdus numarul "+d.floatValue());

                       

                                    System.in.read();

                        }catch(IOException e){}

            }

}

 

            Clasa System are atributul final (nu poate fi extinsa). Ea defineste o interfata de apeluri sistem, independenta de platforma. Toate metodele sunt statice. Contine variabilele statice in, out si err care desemneaza fisierele standard de intrare, iesire si de erori. Contine o functie arraycopy care copiaza un tablou sau o portiune de tablou intr-un alt tablou. Elementele tablourilor pot fi de tip Object sau derivate din acesta.

            Manevrarea sirurilor de caractere se face prin clasele String si StringBuffer care sunt finale. Un obiect de tip String se poate crea direct,

 

            String sir=”exemplu”; sau

            String sir=new String(“exemplu”);

 

            Clasa StringBuffer este utilizata in cazul in care se folosesc sirurui de caractere care se modifica. Urmatorul program exemplifica una dintre posibilitatile de lucru cu cele doua tipuri de siruri de caractere. Tipul sir de caractere a aparut si va apare foarte des in programe.

 

import java.io.*;

public class ShowString

{

            public static String invers1(String S){

                        int len=S.length();

                        StringBuffer SB=new StringBuffer(len);

                        for(int i=S.length()-1;i>=0;i--)

                                    SB.append(S.charAt(i));

                        return SB.toString();

            }

            public static String invers2(String S){

                        StringBuffer SB1=new StringBuffer(S);

                        StringBuffer SB2=new StringBuffer();

                        SB2=SB1.reverse();

                        return SB2.toString();

            }

            public static void main (Stringas args)

            {

                        String S=new String("abcdefghijklmn");

                        System.out.println("Sirul initial este "+S.toString());

                        System.out.println("Raspuns 1 :"+invers1(S));

                        System.out.println("Raspuns 2 :"+invers2(S));

            }

}

 

            Programul declara si foloseste doua functii de inversare a unui sir de caractere. Ambele primesc ca argument un String si folosesc ca variabila intermediara o instanta a clasei StringBuffer.

 

            1.3.2 Pachetul java.util

 

            Dintre clasele acestui pachet amintim StringTokenizer, Vector si Stack. Clasa StringTokenizer este un analizor pe siruri de caractere. Se refera la elemente precum numere, nume, operatori, delimitatori. Delimitatorii impliciti sunt : tt, tr tn dar se pot specifica si delimitatori noi. Programul urmator foloseste caracterul ‘;’ pentru a delimita instructunile din cadrul unui sir de caractere.

 

import java.io.*;

import java.util.StringTokenizer ;

public class ShowToken

{

            public static void main (Stringas args)

            {

                        String SirAnalizat=new String("int a=23;float b=5.5;float x=a+b;b=7-x;");

                        StringTokenizer Analizor=new StringTokenizer(SirAnalizat,";");

                        System.out.println("Sirul analizat este "+SirAnalizat);

                        System.out.println("Sunt "+Analizor.countTokens()+" instructiuni");

                        try{

                                    while (Analizor.hasMoreTokens())

                                                System.out.println(Analizor.nextToken());

                        }catch(Exception e){System.out.println(e);}

                        try{

                                    System.in.read();

                        }catch(IOException e){}

            }

}

 

            Urmatorul program exemplifica utilizarea instantelor de tip Vector. Aceasta clasa se foloseste in cazul in care este nevoie de vectori care isi modifica dimensiunea (altfel tablourile obisnuite sunt suficiente). In definitia acestei clase, elemetele sunt de tip Object si pot fi inlocuite cu orice tip derivat din acesta. Clasa poseda metodele capacity() si size() care intorc capacitatea si respectiv dimensiunea actuala a vectorului. Clasa poseda diferite metode care permit adaugarea sau inserarea de elemente. Daca dimensiunea vectorului este egala cu capacitatea sa si se adauga un nou element atunci capacitatea vectorului se va mari implicit, fara actiunea programatorului. In constructor se poate preciza incrementul de crestere a capacitatii. Declaratia

 

            Vector vec=new Vector(10,3);

 

precizeaza un vector care are 10 elemente initial si posibilitatea de a-si incrementa la nevoie capacitatea cu 3 elemete.

            Programul urmator exemplifica utilizarea tipului Vector. Vom utiliza un vector de persoane.

 

            class Persoana

{

            private String nume;

            private int virsta;

            public void SetNume(String S){

                        nume=S;

            }

            public void SetVirsta(int n){

                        virsta=n;

            }

            public Persoana(String S,int v){

                        nume=new String();

                        SetNume(S);

                        SetVirsta(v);

            }

            public String GetNume(){

                        return nume;

            }

            public int GetVirsta(){

                        return virsta;

            }

            public String toString(){

                        String S=new String("Persoana "+nume+" are virsta "+virsta);

                        return S;

            }

}

import java.io.*;

import java.util.*;

public class ShowVector

{

            private Vector vector;

            BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

            public ShowVector(int capacitate, int pas){

                        vector=new Vector(capacitate,pas);

            }

            public void Introducere(){

                        String nume=new String();

                        String virsta=new String();

                        int v;

                        do{

                                    System.out.println("Nume Persoana (Enter=Sfirsit) : ");

                                    try{

                                                nume="";

                                                nume=br.readLine();

                                                if (nume.equals("")) break;

                                                System.out.println("Virsta Persoana : ");

                                                virsta=br.readLine();

                                                vector.addElement(new Persoana(nume,Integer.parseInt(virsta)));

                                    }

                                    catch(IOException e){System.err.println("Eroare de I/O "+e);}

                        }while(true);

            }

            public void Listare(){

                        System.out.println("tnListare elemente din vector :tn");

                        for(int i=0;i<vector.size();i++)

                                    System.out.println(((Persoana)vector.elementAt(i)).toString());                                

            }

            public static void main (Stringas args)

            {

                        ShowVector show=new ShowVector(10,5);

                        show.Introducere();

                        show.Listare();

                        show.Introducere();

                        show.Listare();

                        try{

                                    System.in.read();

                        }

                        catch(IOException e){System.err.println("Eroare de I/O "+e);}

            }

}

 

            Metoda addElement() adauga un element pe ultima pozitie din vector. Pentru a memora sau modifica un element pe pozitia i se foloseste metoda setElementAt(E,i). Inserarea pe pozitia i se face cu metoda insertElementAt(E,i). Metoda removeElementAt(i) sterge elementul de pe pozitia i. Metoda contains(E) verifica exiastenta unui anumit element pritre elementele vectorului.

 

1.4  Tratarea firelor de execuţie

 

Aplicaţiile moderne trebuie să permită în anumite situaţii execuţia simultană a mai multor prelucrări diferite (afişare imagini, preluare fişiere din reţea, etc). Limbajul Java posedă un mecanism care permite programarea unor activităţi simultane sub forma firelor de execuţie (thread-uri). Toate firele de execuţie dintr-un program (proces) împart acelaşi spaţiu de adrese (aceleaşi variabile). Acest aspect uşurează comunicarea între firele de execuţie prin intermediul unora dintre variabile. Utilizarea firelor de execuţie nu micşorează timpul de execuţie al aplicaţiilor ci le fac mai uşor de scris deoarece se poate scrie câte un program pentru fiecare activitate ce trebuie controlată de către aplicaţie.

Mecanismul de fir de execuţie este descris de calasa Thread din biblioteca java.lang. Clasa Thread conţine metoda run() care, aici, nu este implementată. Acest lucru trebuie făcut într-o clasă care extinde clasa Thread. Firele de execuţie ale aplicaţiei vor fi instanţe ale acestei noi clase. Codul care va fi executat de către firul de execuţie este conţinut în metoda run().

 

            Class NewThread1 extends Thread{

                        public void run(){

                        …..

                        }

                        ……

            }

 

            Aceasta este o primă metodă de definire a firelor de execuţie. O a doua metodă constă în “adăugarea la o clasă a facilităţilor din interfaţa Runnable care are prevăzută, deasemenea, metoda run().

 

            Class NewThread2 implements Runnable{

                        public void run(){

                        ….

                        }

                        …..

            }

 

            În primul caz, instanţierea unui fir de execuţie se face sub forma

 

            NewTread1 fir = new NewThread1();

 

iar în al doilea caz

 

            NewThread2 f  = new NewThread2();

            Thread fir = new  Thread(f);

 

            În cazul firelor de execuţie instanţiate după a doua metodă nu se pot apela în metoda run() metode specifice obiectelor Thread (sleep(), setPriority(), getPriority()). Deşi firul de execuţie este instanţiat, execuţia sa nu poate începe decât după invocarea metodei start().

 

            fir.start();

           

Ca urmare a execuţiei acestei metode firul de execuţie este pregătit pentru execuţie dar aceasta nu începe imediat. În cazul în care firul este creat după prima metodă şi se doreşte pornirea lui imediat după instanţiere atunci constructorul clasei care extinde pe Thread trebuie să conţină apelul lui start().

 

            public NewThread1 (<lista parametri>){

                        Super(<lista parametri>);

                        Start();

            }

 

            Dacă există mai multe fire de execuţie care se execută pe un singur procesor, se pune problema alegerii firului care se execută la un moment dat. Aceasta se face în funcţie de prioritatea firelor de execuţie. Orice cod Java se execută într-un fir de execuţie. La creare, un fir de execuţie are un nivel de prioritate egal cu cel al firului din care a fost creat. Prioritatea i se poate modifica prin metoda setPriority() (se pot da valori între Thread.Min_Priority = 1 şi Thread.Max_Priority = 10). Execuţia a două fire cu aceeaşi prioritate se face în mod diferit funcţie de sistemul de operare pe care se execută.

            Dacă sistemul de operare admite divizarea timpului atunci fiecărui proces i se alocă o cuantă de timp după care este întrerupt şi execuţia continuă cu alt fir. În sistemele fără divizare a timpului, un fir de execuţie cedează controlul procesorului prin apelul explicit al metodei yield() sau prin apelul unei metode care conduce la autoblocare sleep(), wait().

            Un fir de execuţie poate fi într-una din situaţiile : creat, gata de execuţie, suspendat, terminat.

 

1.4.1 Sincronizarea firelor de execuţie

 

            În cazul când două procese se execută simultan, probelma sincronizării apare dacă cele două procese concură la utilizarea aceloraşi resurse sau dacă cooperează între ele.

            Dacă procesele, în cazul nostru firele de execuţie, sunt în concurenţă atunci este necesar ca gestionarea resurselor partajate să se facă consistent. Aceasta înseamnă că la un moment dat un singur fir are acces la resursele comune. Rolul sincronizării             este acela de a asigura accesul exclusiv la resursele comune. Secvenţa de cod care asigură accesul exclusiv se numeşte regiune critică.

            Dacă procesele cooperează atunci ele trebuie să schimbe informaţii. În acest caz sincronizarea presupune ca procesele să se aştepte unul pe altul până când îşi pot transmite informaţiile necesare. Regiunea critică conţine acel cod program care asigură schimbul de informaţie.

            Să considerăm cazul firelor de execuţie concurente. Primul exemplu este cel a două instanţe ale unei clase, FirNeSyncro, care extinde clasa Thread. Partajarea aceleiaşi resurse, anume o instanţă a clasei AccesComun va genera rezultate eronate. Pentru a se observa mai bine acest aspect am introdus blocajul firului curent prin apelul metiodei sleep().

 

public class AccesComun

{

            int a,b;

            public AccesComun(){

                        a=0;b=0;

            }

            void metoda(int numar){

                        Thread t=Thread.currentThread();

                        System.out.println("Din firul "+numar+": a="+a+" b="+b);

                        a++;

                        try{

                                    t.sleep((int)Math.random()*1000);

                        }catch (InterruptedException e){}

                        b++;

            }

}

class FirNeSyncro extends Thread

{

            private AccesComun A;

            private int numar;

           

            public FirNeSyncro(AccesComun A, int numar){

                        this.A=A;this.numar=numar;

            }

            public void run(){

                        for(int i=0;i<3;i++)

                                    A.metoda(numar);

                        System.out.println("Firul "+numar+" Gata !");

            }

}

public class NeSynchro

{

            public static void main (Stringas args)

            {

                        AccesComun A=new AccesComun();

                        new FirNeSincro(A,1).start();

                        new FirNeSincro(A,2).start();

                        System.out.println("Gata main!");

            }

}

 

            Rezultatul rulării este :

 

            Din firul 1 : a = 0, b = 0

            Din firul 2 : a = 0, b = 0

            Din firul 1 : a = 2, b = 1

            Din firul 2 : a = 3, b = 2

            Din firul 1 : a = 4, b = 3

            Din firul 2 : a = 5, b = 4

 

şi arată că cele două variabile a şi b nu sunt actualizate corect.

            Soluţia corectă presupune sincronizarea accesului la obiectul partajat şi se realizează cu ajutorul secvenţelor sau metodelor ce se execută în excludere mutuală. Implementarea foloseşte un mecanism de tip zăvor. Acesta presupune declararea unor metode sau secvenţe de cod sincronizate (atributul synchronized). Pentru a executa metoda sau secvenţa declarată synchronized firul de execuţie închide zăvorul. Cât timp zăvorul este închis nici un alt fir nu poate executa metoda sau secvenţa respectivă. Un fir de execuţie care încearcă execuţia unei metode sau secvenţe “închise” este blocat şi trecut într-o coadă de aşteptare. Când se termină execuţia unei metode cu atributul synchronized zăvorul este deschis şi se preia din coda de aşteptare unul dintre firele blocate care trece în starea gata de execuţie.

           

class FirSyncro extends Thread

{

            static int a=0,b=0;

            static Object o=new Object();

           

            public FirSyncro(String nume){

                        this.setName(nume);

            }

            void metoda(){

                        System.out.println(this.getName()+" : a="+a+" b="+b);

                        a++;

                        try{

                                    sleep((int)Math.random()*1000);

                        }catch (InterruptedException e){}

                        b++;

            }

            public void run(){

                        for(int i=0;i<3;i++)

                                    synchronized(o){

                                                metoda();

                                    }

                        System.out.println(this.getName()+" Gata !");

            }

}

           

public class Synchro2

{

            public static void main (Stringas args)

            {

                        new FirSyncro("Primul fir").start();

                        new FirSyncro("Al doi-lea fir").start();

            }

}

 

            Obiectul o are doar rolul de a furniza zăvorul. Zăvorul poate fi asociat unei clase

 

            synchronized(getClass()){

                        metoda();

            }

 

sau chiar funcţiei metoda(),

 

            synchronized void metoda(){

            ….

            }

 

Dacă metoda ar fi statică atunci zăvorul ar fi asociat chiar clasei din care face parte.

Execuţia programului va actualiza concomitent, deci corect, variabilele a şi b.

 

Următorul exemplu consideră cazul proceselor care colaborează. Instanţele claselor Profesor şi Elev sunt fire de execuţie care comunică prin intermediul unei instanţe a clasei ZonaTampon. Această clasă conţine date pentru coeficienţii coeficienţii unei ecuaţii şi valoarea rădăcinii. Profesorul propune o ecuaţie iar elevul o rezolvă. Elevul nu poate rezolva ecuaţia până când nu primeşte datele necesare în instanţa ZonaTampon. Profesorul nu propune o altă ecuaţie până când nu primeşte rezultatul la problema pusă. Cheia programului se află în clasa ZonaTampon care conţine două metode sincronizate.

 

public class Ecuatie

{

            public static void main (Stringas args)

            {

                        ZonaTampon Tampon_1=new ZonaTampon();

                        Profesor profesor_1=new Profesor(Tampon_1,1);

                        Elev elev_1=new Elev(Tampon_1,1);

                        profesor_1.start();

                        elev_1.start();

                        System.out.println("======================"+"tn");

                        ZonaTampon Tampon_2=new ZonaTampon();

                        Profesor profesor_2=new Profesor(Tampon_2,2);

                        Elev elev_2=new Elev(Tampon_2,2);

                        elev_2.start();

                        profesor_2.start();

            }

}

            public class ZonaTampon

{            private float a,b,valoare;

            private boolean disponibil;

 

            public ZonaTampon(){

                        a=0;b=0;valoare=100000;disponibil=false;

            }

            public synchronized float a_Calculat(int numar){

                        if (!disponibil){

                                    try{

                                                wait();

                                    }catch (InterruptedException e){}

                        }

                        valoare=-b/a;

                        System.out.println(" iar elevul "+numar+" a calculat "+valoare+"tn");

                        disponibil=false;

                        notify();

                        return valoare;

            }

            public synchronized void a_Propus(float a,float b,int numar){

                        if (disponibil){

                                    try{

                                                wait();

                                    }catch (InterruptedException e){}

                        }

                        this.a=a;this.b=b;valoare=100000;

                        System.out.print("Profesorul "+numar+" a propus "+a+"x+"+b+"=0");                           

                        disponibil=true;

                        notify();

            }

}

            class Elev extends Thread

{

            private ZonaTampon Tampon;

            private int numar;

           

            public Elev(ZonaTampon T,int n){

                        Tampon=T;numar=n;

            }

            int getNumar(){

                        return numar;

            }

            public void run(){

                        float valoare;

                        for(int i=1;i<=3;i++){

                                    valoare=Tampon.a_Calculat(getNumar());

                                   

                        }

            }

}

            class Profesor extends Thread

{

            private ZonaTampon Tampon;

            private int numar;

           

            public Profesor(ZonaTampon T, int n){

                        Tampon=T;numar=n;

            }

            int getNumar(){

                        return numar;

            }

            public void run(){

                        for(int i=1;i<=3;i++){

                                    Tampon.a_Propus(i,i+1,getNumar());

                                   

                                    try{

                                                //System.out.print("Profesorul "+numar+" a propus "+i+"x+"+(i+1)+"=0");                        

                                                sleep(1000);

                                    }catch (InterruptedException e){}

                        }

            }

}

 

 

1.5  Intrări/Ieşiri în Java

 

În limbajul Java operaţiile de intrare/ieşire utilizează fluxuri (stream-uri). În general, un flux este un obiect care poate să producă sau să primească octeţi. Clasele care descriu operaţiile de intrare/ieşire se află în biblioteca java.io. Ele formează ierarhii determinate de specificul fluxului de intrare sau ieşire, fluxuri de octeţi sau de caractere. Spre exemplu, clasele InputStream şi OutputStream sunt specializate în manevrarea fluxurilor de octeţi iar clasele Reader şi Writer lucrează la nivel de caracter.

Cu o singură excepţie, toate clasele implementează doar operaţii de scriere sau operaţii de citire. Excepţia o constitue clasa RandomAccessFile care are atât metode de scriere cât şi metode de citire.

 

1.5.1 Intrări/Ieşiri la nivel de octet

 

Clasele InputStream şi OutputStream sunt abstracte. Ele se extind pentru a obţine câteva tipuri utile de fluxuri. În aceste clase nu se face referire la tipul fluxului. Astfel se pot face citiri dintr-un fişier sau vector, din reţea. Modul diferit în care se specifică sursa de octeţi determină diferitele clase care extind clasa InputStream. Acelaşi lucru se poate spune despre clasa OutputStream şi ierarhia dezvoltată din aceasta.

Clasele FileInputStream şi FileOutputStream se referă la lucrul cu fişiere. Clasele ByteArrayInputStream şi ByteArrayOutputStream citesc/scriu în/dintr-un vector. Alte două perechi de clase sun BufferedInputStream şi BufferedOutputStream care folosesc fiecare câte o zonă tampon care optimizează operaţiile de intrări/ieşiri. Dacă se doreşte să se citească sau să se scrie date din tipurile primitive ex. byte, int, float, double, se vor folosi clasele DataInputStream şi DataOutputStream.

Toate metodele din clasele InputStream şi OutputStream sunt declarate cu throws IOException (deci generează excepţia generală de intrare/ieşire). Această declaraţie este prezentă şi în clasele derivate.

Următorul program face citirea şi scrierea la nivel de octet, din/în fişiere text.

 

import java.io.*;

public class FileCopy

{

            /*

             Copiere fisiere. Citirea/Scrierea se face la nivel de octet.

             */

            public static void main (Stringas args)

            {

                        if (args.length==2){

                                    try{

                                                FileInputStream in=new FileInputStream(argsa0s);

                                                FileOutputStream out=new FileOutputStream(argsa1s);

                                                int b;

                                                while ((b=in.read())!=-1)

                                                            out.write(b);

                                                out.close();

                                    }catch (IOException e){System.out.println("Eroare de IO !");}

                        }

            }

}

 

            Funcţiile read() şi write() lucrează cu date de tip int ştiindu-se faptul că, caracterele dintr-un fişier text sunt în codul Unicode (pe doi octeţi).

            Următorul program exemplifică operaţii de scriere şi citire cu fluxuri ce utilizează vectori (in cazul de faţă vectori cu elemente de tip byte).

 

import java.io.*;

public class Copy

{

            public static void copy(InputStream inp1,InputStream inp2, OutputStream out){

                        byte b;

                        try{

                                    while((b=(byte)inp1.read())!=-1)      

                                                out.write(b);

                                    while((b=(byte)inp2.read())!=-1)

                                                out.write(b);

                                   

                        }catch(IOException e){}

            }

            public static void main (Stringas args)

            {

                        byte sursa1as={(byte)'a',(byte)'b',(byte)'c',(byte)'d'};

                        byte sursa2as={(byte)'e',(byte)'f',(byte)'g',(byte)'h'};

                        ByteArrayInputStream fluxIn1=new ByteArrayInputStream(sursa1);

                        ByteArrayInputStream fluxIn2=new ByteArrayInputStream(sursa2);

                        ByteArrayOutputStream fluxOut=new ByteArrayOutputStream();

                        copy(fluxIn1,fluxIn2,fluxOut);

                        System.out.println("Flux-ul iesire este este : "+fluxOut);

            }

}

 

            În cazul când se lucrează cu fluxuri la nivel de octet, deobicei nu se foloseşte un singur flux ci mai multe fluxuri suprapuse,

 

            BufferedInputStream br = new BufferedInputStream(new System.in);

 

            Programul următor exemplifică acest aspect. Concatenează mai multe fişiere. Intrarea este o secvenţă de fluxuri (fişiere), cu zonă tampon, precizate în linia de comandă. Ieşirea este tot fişier cu zonă tampon.

 

import java.io.*;

import java.util.*;

public class ConcFis

{

            public static void main (Stringas args)

            {

                        if (args.length<3){

                                    System.out.print("Utilizare : ConcFis <Fis. iesire>");

                                    System.out.println(" <Fis. intrare 1> <Fis. intrare 2>...");

                                    System.exit(1);

                        }

                        try{

                                    /* Se pregateste secventa de fluxuri (fisiere) de intrare */

                                   

                                    FileInputStream VectorFluxIntrareas=new FileInputStreamaargs.length-1s;

                                    for(int i=0;i<args.length-1;i++)

                                                VectorFluxIntrareais=new FileInputStream(argsai+1s);

                                    Vector fisiereIntrare=new Vector(args.length-1);

                                    for(int i=0;i<args.length-1;i++)

                                                fisiereIntrare.addElement(new BufferedInputStream(VectorFluxIntrareais));

                                    SequenceInputStream intrare=new SequenceInputStream(fisiereIntrare.elements());

                                   

                                    /* Iesirea este un fisier cu zona tampon */

                                   

                                    BufferedOutputStream rez=new BufferedOutputStream(new FileOutputStream(argsa0s),1024);

                                    int cit;

                                    /* Citire/Scriere la nivel de octet */

                                    while ((cit=intrare.read())!=-1)

                                                rez.write(cit);

                                    /* Citire/Scriere cu buffer */

                                    byte bas=new bytea512s;

                                    while ((cit=intrare.read(b))>0){

                                                rez.write(b);

                                                rez.flush();

                                    }                                 

                                    rez.close();

                                    intrare.close();

                        }catch(IOException e){System.err.println("Eroare de I/O"+e);}

            }

}

 

1.5.2 Intrări/Ieşiri la nivel de caracter

 

Clasele Reader şi Writer sunt şi ele abstracte şi stau la baza unei ierarhii de clase pentru intrări/ieşiri la nivel de caracter. Pe lângă aceasta, cele două clase permit sincronizarea operaţiilor asupra fluxurilor derivate. Aceasta prin intermediul unei instanţe Object membră în aceste două clase. Clasele InputStreamReader şi OutputStreamWriter realizează o punte între fluxurile de caractere şi cele de octeţi. Ele citesc/scriu caractere traducându-le în octeţi.

Programul următor realizează copia unui fişier. Citirile-scrierile se fac la nivel de caracter sau linie. Se folosesc fluxuri suprapuse care în final posedă zonă tampon.

 

import java.io.*;

public class CopyFile2

{

            public static void Copyfile(String NameFileIn,String NameFileOut){

                        try{

                                                BufferedReader br=new BufferedReader(new FileReader(NameFileIn));

                                                BufferedWriter bw=new BufferedWriter(new FileWriter(NameFileOut));

                                               

                                        String linie;

                                               

                                       while ((linie=br.readLine())!=null){

                                                   bw.write(linie);

                                                   bw.newLine();

                                                   linie="";

                                       }

                                       bw.close();

                        }catch (IOException e){System.out.println("Eroare I/O");};

            }

                          

            public static void main (Stringas args)

            {

                        Copyfile("Fis1.txt","Fis2.txt");

            }

}

 

1.5.2 Intrări/Ieşiri la nivel de caracter

 

Clasele DataInputStream şi DataOutputStream oferă posibilitatea să se transfere prin fluxuri date primare precum boolean, char, int, float, etc. Utilizând metodele acestor clase, informaţia este transferată într-un format independent de sistem. Totuşi, pentru ca datele să fie citite cu DataInputStream trebuie să fi fost scrise cu DataOutputStream.

Programul următor exemplifică citirea/scrierea datelor primitive de tipuri diferite.

 

/**

 Fluxuri de date si fisiere. Fisierul (fluxul) de intrare poate fi

si tastatura. Se citesc si se scriu in fisierul (fluxul) de iesire

nr_intregi,nr_flotanti si intregii, respectiv flotantii corespunzatori.

 */

import java.io.*;

public class FluxDate

{

            public static void main (Stringas args)

            {

                        if (args.length<1){

                                    System.out.print("Utilizare : FluxDate <fis. iesire>");

                                    System.out.println(" <Fis. intrare> (optional)");

                                    System.exit(1);

                        }

                        try{

                                    OutputStream fluxIesire=new FileOutputStream(argsa0s);

                                    DataOutput fluxIesireDate=new DataOutputStream(fluxIesire);

                                    InputStream fluxIntrare;

                                    DataInput fluxIntrareDate;

                                    if (args.length==2){

                                                fluxIntrare=new FileInputStream(argsa1s);

                                    }else{

                                                fluxIntrare=System.in;

                                    }

                                    fluxIntrareDate=new DataInputStream(fluxIntrare);

                                    int nrInt=fluxIntrareDate.readInt(),nrFloat=fluxIntrareDate.readInt();

                                    fluxIesireDate.writeInt(nrInt);

                                    fluxIesireDate.writeInt(nrFloat);

                                    int i,cit;

                                    for(i=0;i<nrInt;i++){

                                                cit=fluxIntrareDate.readInt();

                                                fluxIesireDate.writeInt(cit);

                                    }

                                    float a;

                                    for(i=0;i<nrFloat;i++){

                                                a=fluxIntrareDate.readFloat();

                                                fluxIesireDate.writeFloat(a);

                                    }

                                    fluxIntrare.close();

                                    fluxIesire.close();

                        }catch (IOException e){System.err.println("Eroare de I/O");}

            }

}

 

            4. Aplicaţii de reţea în Java

 

            4.1 Noţiuni generale despre reţelele de calculatoare şi protocoale de comunicaţie

 

     O reţea de calculatoare este un sistem de comunicaţie care conectează mai multe noduri (hosts). Un nod poate fi un calculator sau un alt dispozitiv care poate recepţiona date (ex. un telefon mobil, imprimantă). Pentru a fi conectate în reţea, nodurile au nevoie de un dispozitiv numit interfaţă sau controller. Unele noduri au o singură interfaţă (noduri monopunct), altele dispun de mai multe astfel de dispozitive (noduri multipunct). Nodurile se deosebesc între ele, în primul rând, prin rolul pe care îl joacă în reţea. Astfel, unele noduri sunt specializate, spre exemplu servere de fişiere sau servere Web, iar altele sunt de uz general accesibile pentru diverse sevicii pentru unul sau mai mulţi utilizatori interactivi.

            Din punct de vedere al acoperirii geografice exdistă două tipuri de reţele.

            O reţea locală (Local Area Network – LAN) conectează calculatoare apropiate geografic : în aceeaşi sală sau clădire, în aceeaşi instituţie (eventual cu mai multe clădiri). Calculatoarele pot fi situate în acest tip de reţea şi la câţiva kilometri distanţă. Legătura se realizează de regulă prin cablu şi beneficiază de viteze mari de transfer : 10 Mbps-100 Mbps sau 1 Gbps.

            O reţea răspândită geografic (Wide Area Network - WAN) conectează calculatoare aflate la mari distanţe geografice, chiar în ţări şi continente diferite. Legăture se realizează prin linii telefonice, canale radio sau satelit. Vitezele de transfer sunt mai mici decât în cazul reţelelor locale : zeci de Kbps sau sute de Mbps (cea mai utilizată valoare este 155 Mbps).

            În prezent se foloseşte termenul de intranet pentru a desemna o reţea locală la nivelul unei instituţii. Prin internet se înţelege reţeaua WAN întinsă în prezent la nivel mondial. Ea este realizată prin interconectarea tuturor reţelelor de pe glob pe baza familiei de protocoale TCP/IP. Prin interconecatare înţelegem faptul că există un nod mulipunct care face parte din mai multe reţele fiind conectat în fiecare reţea prin unul dintre punctele sale.

 

            4.1.1 Modele de protocoale

 

            Comunicarea între calculatoarele dintr-o reţea are nevoie de existenţa unor protocoale de comunicare. În general, prin protocol înţelegem un set de reguli şi convenţii stabilite între participanţii la o activitate comună, în cazul de faţă transferul de informaţii între calculatoare.

            Fiind deosebit de complexe, protocoalele utilizate în reţelele de calculatoare sunt proiectate pe niveluri sau straturi (layers). Fiecare nivel defineşte o serie de servicii şi protocoalele corespunzătoare. Există astfel mai multe modele de protocoale, cele mai cunoscute sunt OSI şi TCP/IP.

            Modelul OSI (Open System Interconection) este mai mult un ghid decât o specificare. Este structurat pe 7 niveluri. Enumerăm nivelurile de la cel mai de jos la cel mai înalt.

            Nivelul fizic realizează transferul biţilor printr-un canal de comunicare. Apar probleme precum : reprezentarea fizică a unui bit, dacă se poate face transmisie simultană în ambele sensuri, stabilirea şi terminarea unei conexiuni.

            Nivelul legăturii de date transmite cadre de date (sute sau mii de biţi) de la sursă la destinaţie şi cadre de confirmare de la destinaţie la sursă. Pentru delimitare se ataşează secvenţe speciale de biţi la începutul şi sfârşitul unui cadru.

            Nivelul reţea transferă pachete de date. Fiecare pachet conţine atât datele care se transmit cât şi adresele de identificare a sursei şi destinaţiei. Sunt cazuri când pachetele traversează mai multe reţele pentru a ajunge de la sursă la destinaţie.

            Nivelul transport acceptă date la nivelul sesiune. Se crează câte o conexiune de reţea distinctă pentru fiecare conexiune de transport solicitată de nivelul sesiune. La nivelul transport comunicarea se face între vecini imediaţi în reţea. Această comunicare este capăt la capăt adică un program din calculatorul sursă conversează cu un program din calculatorul destinaţie.

            Nivelul sesiune furnizează servicii suplimentare faţă de cel transport. Un serviciu este controlul dialogului care stabileşte dacă se poate realiza trafic simultan în ambele sensuri. Un alt serviciu este cel care permite ca în caz de eşec,  o operaţie să poată fi reluată din punctul de întrerupere şi nu de la început.

            Nivelul prezentare priveşte sintaxa şi semantica informaţiilor  transmise de la o aplicaţie la alta. Se stabileşte o codificare standard a informaţiei transportate şi o standardizare a structurilor de date.

            Nivelul aplicaţie conţine cea mai mare diversitate de protocoale deoarece fiecare apliacţie poate defini propriul protocol.

 

            Modelul TCP/IP este, de fapt, primul apărut. A fost definit în legătură cu prima reţea de calculatoare realizată în jurul anului 1970 la Departamentul Apărării al SUA. Numele provine de la cele două protocoale principale ale sale. Comaparând cu modelul OSI se poate observa că nu sunt definite niveluri distincte pentru sesiune şi prezentare iar nivelurile fizic şi legătură de date sunt cuprinse într-unul singur numit nod – reţea. Apare nivelul internet destinat comunicării de pachete. Nodurile emit pachete de date care circulă independent până la destinaţie. Ele pot ajunge la destinaţie în altă ordine decât cea în care au fost trimise. Rearanjarea lor va fi sarcina nivelurilor superioare.

            Nivelul transport corespunde celui de la modelul OSI. Sunt definite la acest nivel două protocoale mai importante anume TCP (Transmission Control Protocol) care realizează transmisii cu conexiune şi UDP (User Datagram Protocol) care corespunde transmisiilor fără conexiune de tip cap la cap.

            Nivelul aplicaţie apare imediat deasupra celui transport. La acest nivel cele mai cunoscute protocoale sunt : TELNET pentru transmisii virtuale (sesiune la distanţă), FTP pentru transfer de fişiere, SMTP pentru poştă electronică, HTTP pentru hipertext (pagini Web).

 

            Există o serie de probleme comune tuturor familiilor de protocoale. Dintre acestea o vom menţiona pe aceeea care priveşte serviciile pe care un nivel le oferă nivelurilor superioare. Ne vom referi doar la serviciile pe care nivelul transport îl oferă nivelului aplicaţie (in cazul modelului TCP/IP). Aceste servicii sunt :

-         cu conexiune sau fără conexiune

-         secvenţiere

-         controlul erorilor

-         controlul fluxului

-         flux de octeţi sau mesaje

-         full-duplex sau half-duplex

 

Un serviciu cu conexiune presupune că cele două programe de aplicaţie stabilesc o conexiune logică înainte de a începe transferul de date. Se efectuează trei operaţii : stabilirea conexiunii, transferul de mesaje (date) şi terminarea conexiunii.

Serviciul fără conexiune sau serviciu de datagrame transmite independent fiecare mesaj. Mesajele trebuie să conţină toate informaţiile necesare unui transfer efectuat în condiţii optime.

Secvenţializarea presupune că receptorul primeşte pachetele de datele în ordinea în care au fost trimise. Aceasta, în ciuda faptului că este posibil ca două pachete consecutive să urmeze rute diferite şi pachetul care a plecat al doilea să ajungă primul.

Controlul erorilor asigură faptul că programele aplicaţie primesc date corecte, fără erori.

Controlul fluxului nu permite emiţătorului să trimită date mai rapid decât poate recepţiona destinatarul.

Serviciul de flux de octeţi nu conţine informaţii care să indice limitele mesajelor. Protocoalele cu conexiune lucrează de obicei în acest mod.

Conexiunea full-duplex permite transfer simultan în ambele direcţii. În cazul half-duplex se permite la un moment dat transfer numai într-o direcţie.

4.1.2 Familia de protocoale utilizate în Internet

 

            Protocoalele utilizate în prezent în Internet permit interconectarea oricăror tipuri de reţele inclusiv  reţelele LAN şi WAN. Protocoalele Internet reprezintă o suită de protocoale dintre care cele mai cunoscute sunt TCP şi IP. Aceste două protocoale sunt de nivel inferior. Suita de protocoale Internet conţine şi protocoale de nivel superior care specifică aplicaţii comune precum poşta electronică, transferul de fişiere, accesul la fişiere hipertext (pagini Web), etc. Le vom enumera în ordinea crescătoare a plasării lor pe niveluri în comparaţie cu medelul OSI.

            Protocoalele ARP (Address Resolution Protocol) şi RARP (Reverse Address Resolution) se pot plasa între nivelurile 2 şi 3 din OSI. ARP realizează corespondenţa între o adresă Internet şi o adresă hardware iar RARP realizează corespondenţa inversă.

            La nivelul 3 OSI sunt plasate protocoalele IP (Internet Protocol) şi ICMP (Internet Control Message Protocol). Protocolul IP pune la dispoziţia protocoalelor de nivel superior un serviciu de transfer de pachete. ICMP este utilizat pentru transmiterea informaţiilor de comandă şi de eroare între componentele reţelei.

            Protocoalele TCP şi UDP corespund nivelului 4 din OSI. Protocolul TCP este orientat pe conexiune şi transmite fluxuri de octeţi, full-duplex. Pe acest protocol se bazează cele mai multe programe de aplicaţii din Internet. UDP este un protocol fără conexiune destinat transmiterii de datagrame. Este utilizat în aplicaţiile mai simple din Internet în care fiabilitatea datelor nu este o cerinţă majoră.

Ultimelor trei niveluri din OSI (Sesiune, Prezentare, Aplicaţie) le corespund protocoalele FTP, Telnet , SMTP (Simple Mail Transfer Protocol), SNMP (Simple Network Management Protocol). Protocolul FTP este dedicat transferării de fişiere. Telnet este protocolul care serveşte la realizarea sesiunilor de lucru la distanţă. SMTP oferă servicii de poştă electronică. SNMP semnalează apariţia condiţiilor anormale din reţea şi setează anumiţi parametri de lucru ai reţelei.

 

4.1.2.1 Nivelul IP

Protocolul IP transmite datagrame de la o adresă sursă la o adresă destinaţie. Pe lângă datele care se transmit, o datagramă conţine adresa sursei şi cea a destinaţiei. În acest fel livrarea datagramelor se poate face independent.

Pentru a identifica reţelele şi nodurile, protocolul IP foloseşte adrese Internet reprezentate pe 32 de biţi. Aceste adrese sunt alocate în mod unic la nivel global de către o autoritate centrală şi mai multe autorităţi regionale. Orice nod conectat într-o reţea TCP/IP trebuie să aibă o adresă IP. Adresele IP sunt divizate în 5 clase de adrese, A, B, C, D, E, în funcţie de modul cum sunt utilizaţi cel 4 octeţi ai adresei. Comună este scrierea, fiecare octet fiind notat prin numărul zecimal corespunzător iar cele 4 numere sunt separate prin punct, spre exemplu 172.16.122.204. În general, prima parte a adresei identifică reţeaua iar ultima parte identifică nodul (hostul).

Adresele de clasă A se remarcă prin faptul că primul număr poate fi maxim 127. Se pot adresa maxim 126 de reţele. Pentru desemnarea nodurilor se folosesc 24 de biţi deci se pot adresa 16 milioane de noduri. Astfel de adrese caracterizează organizaţii mondiale sau companii transnaţionale.

Adresele de clasă B folosesc 16 biţi pentru a identifica o reţea şi restu de 16 pentru nodurile din cadrul reţelei. Primii doi biţi din zona destinată reţelei sunt 10 astfel că primul octet este între 128 şi 191. Numărul de reţele ce se pot adresa sunt cca 2e14, fiecare reţea putând avea peste 65.000 de noduri. Astfel de adrese au fost date marilor companii sau universităţi.

Adresele de clasă C folosesc 24 de biţi pentru a desemna reţeaua şi 8 pentru nod. Primul octet este cu valori între 192 şi 223. Se pot identifica cca. 2 milioane de reţele, fiecare reţea având maxim 254 noduri. Sunt cele mai alocate adrese în prezent.

Adresele de clasă D au primul octet cu valori între 224 şi 239. Se numesc adrese multicast şi se folosesc în aplicaţii în care se emit mesaje ce ajung simultan la mai mulţi receptori.

Adresele de clasă E sunt rezervate pentru viitor. Ele au valori peste 240.0.0.0.

 

4.1.2.2 Nivelul TCP şi UDP

Există posibilitatea ca într-un nod mai multe procese utilizator să lucreze simultan cu protocolul TCP sau UDP fiind necesar să se identifice aceste procese. Identificarea se face prin interemediul porturilor care se reprezintă fizic pe 16 biţi. Fiecare din cele două protocoale pot adresa 65536 porturi diferite. Numerele de port apar în antetele TCP şi UDP şi împreună cu adresele IP identifică unic destinţia mesajelor.

Prin convenţie numerele de port mai mici decât 1024 se atribuie unor servicii cunoscute. Programele server îşi înregistrează numerele de port pentru a fi găsite la locaţii fixe. Programele client capătă numere de port efemere toate mai mari decît 1024.

 

4.1.2.3 Nivelul aplicaţie

Protocoalele de nivel aplicaţie sunt deja multe şi sunt în continuă creştere. Amintim câteva dintre acestea.

 

Serviciul DNS al numelor de serviciu se referă la denumirea simbolică a unui nod şi la corespondenţa dintre aceste nume şi adresele Internet.

Există domenii de nume de nivel superior. Fiecare din acestea se împarte ăn subdomeni care la rândul lor se împart în subdomeni…

La nivelul superior (nivelul 1) sunt două categorii de domenii : generice şi nume de ţări. Domenii generice sunt : com (comercial), edu (educaţional), gov (guvernamental), org (organizaţii non-profit), etc. Domeniile nume de ţări constau din 2 litere ex. ro (Romania), uk (Marea Britanie), fr (Franţa). Organizarea de la nivelul 2 depinde de administrarea de la nivelul superior.

Numele de domenii sunt separate de punct. Nu se face distincţie între literele mari şi mici. Lungimea unei componente nu depăşeşte 64 caractere iar întregul nume nu depăşeşte 255.

 

Serviciul de poştă electronică (e-mail) foloseşte  protocolul SMTP care este dedicat transmiterii de mesaje cu conţinut ASCII. Un client este identificat printr-un nume de login şi un nume de domeniu : numelogin@numedomeniu. Serviciul presupune transmiterea şi recepţia mesajelor, editarea mesajelor, accesul la mesajul recepţionat în cutia poştală (mailbox), salvarea mesajelor recepţionate.

Portul rezervat este 25.

 

World Wide Web (WWW) este cea mai cunoscută aplicaţie Internet. Permite transmiterea de documente, desene, fotografii. În WWW se face distincţie între partea de client şi partea de server a aplicaţiei. Un server WWW gestionează documente şi le trimite clienţilor la cererea acestora. Pentru identificarea documentelor s-a propus conceptul de locator universal de resurse (Universal Resource Locator – URL) care se specifică prin

 

protocol : //calculator:port/nume_de _cale#eticheta

 

unde :

-         protocol specifică cum se comunică cu documentul solicitat ; se folosesc http  pentru documente hipertext, ftp pentru transfer de fişiere sau file pentru acces la fişier local.

-         calculator este numele nodului pe care se află documentul

-         port precizează portul la care rulează serverul pentru documentul specificat

-         nume_de_cale este numele de cale al unui fişier

-         etichetă este un element obţional care poate indica o locaţie din cadrul documentului.

 

4.2 Programarea cu sockets în Java

Java este un limbaj dedicat programării pentru reţele. Prin intermediul bibliotecii java.net se implementează o interfaţă mai veche de programare, aceea bazată pe socluri (sockets).

Soclurile sunt un nivel de abstractizare care permite o programare uşoară a reţelelor de calculatoare. Se poate face o analogie între operaţiile cu fişiere într-un sistem de calcul şi comunicarea în reţea. Noţiunii de fişier îi corespunde soclul dar spre deosebire de fişier, soclul există doar în memorie. Câteva particularităţi ale lucrului în reţea sunt :

 

-         relaţia client-server este tipică aplicaţiilor de reţea; ea este asimetrică, deci un program trebuie să cunoască rolul pe care îl joacă în reţea;

-         comunicarea în reţea se face cu conexiune, fiind asemănătoare cu transferurile din cazul fişierelor sau fără conexiune caz în care fircare operaţie poate implica un alt proces , eventual pe un alt calculator;

-         se specifică mai mulţi parametri decât în cazul fişierelor;

 

Interfaţa de programare cu socluri din Java permite comunicarea cu programe scrise în alte limaje care folosesc interfeţe de tip soclu. Teoretic această interfaţă este independentă de protocoalele de comunicaţie folosite dar practic, este strâns legată de protocoalele TCP/IP.

 

4.2.1 Utilizarea adreselor Internet şi a URL-urilor

Pentru lucrul cu identificatorii simbolici (nume) şi cei numerici (adrese numerice) ai calculatoarelor din reţeaua Internet, limbajul Java definşte clasa InetAddress care nu are constructori expliciţi. Menţionăm câteva metode ale acestei clase :

 

-         public static InetAddress getByName(String Host) throws UnknownHostException determină adresa IP a unui calculator căruia i se cunoaşte numele (forma simbolica ex. www.ucv.ro). Se poate preciza adresa Internet (numerică) sub forma unui şir de caractere, ex. “183.224.7.202”. Deoarece întoarce un obiect de tip InetAddress, metoda poate juca rolul de constructor;

-         public static InetAddress getLocalHost() throws UnknownHostException determină adresa IP a calculatorului local. Poate juca rol de constructor;

-         public static InetAddress[] getAllByName(String Host) throws UnknownHostException întoarce într-un tablou toate adresele IP ale  calculatorului specificat prin nume;

-         public String getHostName() întoarce numele calculatorului căruia îi corespunde obiectul InetAddress curent. Dacă returnează null atunci obiectul InetAddress care a apelat metoda se referă la una dintre adresele de reţea ale calculatorului local;

-         public String getHostAddress() întoarce adresa IP a obiectului InetAddress curent sub forma %d.%d.%d.%d.

 

Următorul program determină numele simbolic al calculatorului local şi afişează toate eventualele nume simbolice şi adrese IP ale calculatorului local dacă acesta este multi-punct (nod în mai multe reţele).

 

import java.net.*;

import java.io.*;

public class TestInet

{

      public static void main (String[] args)

      {

            try{

                  InetAddress adresaLocala=InetAddress.getLocalHost();

                  System.out.println("Numele gazdei : "+adresaLocala.getHostName()); 

                  InetAddress vectorAdrese[] =      InetAddress.getAllByName(adresaLocala.getHostName());

                  System.out.println("Adresele IP ale statiei sunt :\n");

                  for(int i=0;i<vectorAdrese.length;i++)

                        System.out.println(vectorAdrese[i].getHostName()+" + "+vectorAdrese[i].getHostAddress());

                  System.in.read();

            }

            catch (IOException e){System.err.println("Exceptie : "+e);}

      }

}

 

            Clasa URL permite lucrul cu locatori universali de resurse, URL.  În clasa URL sunt 4 constructori :

 

-         public URL(String protocol, String host, int port, String file) throws MalformedURLException. Valoarea port = -1 precizează portul standard utilizat de protocolul specificat.

-         public URL(String protocol, String host, String file) throws MalformedURLException. Noul obiect URL foloseşte portul standard pentru protocolul specificat.

-         public URL(URL context,String spec) throws MalformedURLException. Se crează un obiect URL prin interpretarea string-ului spec în contextul specificat.

-         public URL(String spec) throws MalformedURLException crează un obiect URL plecând de la reprezentarea precizată.

 

Alte metode :

 

- public int getPort() întoarce numărul de port al obiectului URL curent sau  –1 dacă numărul de port nu este specificat în respectivul URL;

-       public String getHost()întoarce numele de host al URL-ului curent;

-       public String getFile()întoarce numele de fişier al URL-ului curent;

-       public String getProtocol()întoarce protocolul utilizat de URL-ul curent;

-       public boolean sameFile(URL other)compară URL-ul curent cu un altul;

-       public String toExternalForm()construieşte o reprezentare sub formă de String a URL-ului curent;

-       public URLConnection openConnection()throes IOException returnează un obiect URLConnection care este o conexiune la obiectul URL curent;

-       public final InputStream openStream()throws IOException deschide o conexiune spre URL-ul curent şi întoarce un flux pentru a citi din această conexiune.

 

Prin intermediul clasei URL se pot crea legături de comunicare între o aplicaţie şi o resursă putându-se prelua documente de pe orice server Web. Următorul program realizează o conexiune cu URL-ul specificat în linia de comandă. Se copiază fişierul specificat în declaraţia de URL într-un fişier local al cărui nume este dat tot în linia de comandă. Instanţei URL i se asociază un flux de intrare deschis prin apelul metodei openStream.

 

import java.net.*;

import java.io.*;

public class TestUrl

{

      public static void main (String[] args)

      {

            try{

                  URL url=new URL(args[0]);

                  BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));

                  BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(args[1])));

                  String line=new String();

                  while ((line=br.readLine ())!=null){

                        bw.write(line);

                        bw.newLine();

                  }

                  bw.close();

                 

            }catch(Exception e){System.err.println("Exceptie : "+e);}

      }

}

 

            Clasa URLConnection este cea mai utilizată pentru preluarea documentelor din Internet. Spre deosebire de instanţele URL, instanţele clasei URLConnection pot citi şi scrie la aceeaşi resursă. Astfel, clasa URLConnection permite transmiterea unei cereri către o aplicaţie server şi preluarea răspunsului de la aceasta. Cele două programe care urmează trimit către un servlet o cerere de adunare a două numere întregi. Cererea este furnizată sub forma şirului de caractere

 

            numar=123&numar=456”

 

iar răspunsul servlet-ului Sumator va fi “suma este : 579”.

            Primul program transmite o cerere GET incorporată în cadrul adresei URL, spre exemplu http://nume_host:8080/servlet/Sumator?numar=123&numar=456. Adresa poate fi dată în linia de comandă a programului sau precizată direct ca şir de caractere constant (instrucţiunea trecută în comentariu). Apelul metodei openConnection a clasei URL crează un obiect URLConnection şi deschide conexiunea cu resursa specificată. Prin intermediul acestei instanţe se deschide un flux de intrare asociat fluxului de ieşire al servlet-ului.

 

import java.io.*;

import java.net.*;

public class UrlWrite1{

      public static void main(String[] args) throws Exception {

            try{

                  String adrURL=new String(args[0]);

                  URL url=new URL(adrURL);

                  //URL url = new URL("http://name:8080/servlet/Sumator?numar="+"456"+"&numar="+"123");

                  URLConnection connection = url.openConnection();

                  InputStream in = connection.getInputStream();

                  BufferedReader input = new BufferedReader(new InputStreamReader(in));

                  String line = input.readLine();

                  System.out.println(line);

                  input.close();               

                  System.in.read();

            }catch(IOException e)

            {

                  System.err.println("Eroare I/O "+e);

            }

              

      }

}

 

Al doilea program transmite o cerere POST. El funcţionează după schema clasică :

1.      Crează un URL.

2.      Deschide o conexiune URLConnection.

3.      Setează facilităţile de scriere la URLConnection (metoda setDoOutput).

4.      Deschide un flux de ieşire la conexiunea stabilită (metoda getOutputStream). Acesta este conectat la intrarea standard a servlet-ului.

5.      Scrie la fluxul de ieşire şi apoi îl închide.

6.      Deschide un flux de intrare la conexiunea stabilită (metoda getInputStream). Acesta este conectat la ieşirea standard a servlet-ului.

7.      Citeşte de la fluxul de intrare şi apoi îl închide.

 

import java.io.*;

import java.net.*;

public class UrlWrite2

{

      public static void main(String[] args) throws Exception {

            try{

                  URL url = new URL("http://name:8080/servlet/Sumator");

                  URLConnection connection = url.openConnection();

                  connection.setDoOutput(true);

                  PrintWriter out = new PrintWriter(connection.getOutputStream());

                  out.println("numar=5&numar=7");    

                  out.close();

                  InputStream in = connection.getInputStream();

                  BufferedReader input = new BufferedReader(new InputStreamReader(in));

                  String line = input.readLine();

                  System.out.println(line);

                  input.close();

                  System.in.read();

            }catch(IOException e)

            {

                  System.err.println("Eroare I/O "+e);

            }

      }

}

 

4.2.2 Comunicarea orientată pe conexiune : clasele Socket şi ServerSocket

 

            În cele mai multe cazuri aplicaţiile din Internet folosesc o comunicaţie sigură, un canal de comunicaţie care oferă certitudinea că mesajele care se transmit sunt recepţionate în aceeaşi ordine în care au fost trimise. Aceasta este comunicarea prin conexiune. După stabilirea conexiunii aplicaţiile folosesc canalul de comunicaţie prin intermediul fluxurilor de date. Un canal are nevoie de două fluxuri unidirecţionale (unul de citire, altul de scriere) folosite pentru comunicarea într-un singur sens. La capetele canalului se află câte un soclu (socket) care asigură transmiterea şi recepţia datelor. Cele două socluri identifică unic conexiunea care s-a stabilit şi permit accesul programatorului la conexiune.

Majoritatea aplicaţiilor din Internet sunt de tip client-server. Ele se împart în două grupuri distincte aplicaţiile client şi cele server. Programele server oferă servicii clienţilor acceptând cererile de comunicare ale acestora. Clientul este programul care iniţiază comunicarea şi a cărui funcţionare se bazează pe serviciile oferite de server. Programul client crează un soclu specificându-i adresa programului server şi trimite cererea de conexiune spre acesta. Programul server aşteaptă astfel de cereri  iar după acceptarea unei cereri crează soclul pereche. Conexiunea este stabilită şi transferul de date poate începe.

Folosind clasele Socket şi ServerSocket în limbajul Java se pot scrie aplicaţii client-server bazate pe protocolul TCP/IP. Din existenţa celor două clase deducem faptul că în Java se face distincţie între socketul client şi cel al serverului.

            Clasa Socket se referă la socket-urile client. Constructorii acestei clase sunt :

 

-         public Socket(String host, int port) throws UnknownHostException, IOException crează un socket şi-l conectează la calculatorul şi portul specificat;

-         public Socket(InetAddress address, int port) throws IOException; identitatea calculatorului la distanţă se precizează printr-un obiect InetAddress;

-         public Socket(String host, int port, InetAddress localaddress,int localport ) throws IOException crează un socket şi-l conectează la distanţă la calculatorul şi portul specificat. Socketul este conectat şi local la adresa IP şi portul specificate;

-         public Socket(InetAddress address, int port, InetAddress localaddress,int localport ) throws IOException. Calculatorul la distanţă este precizat prin adresa IP şi portul specificate. Socketul este conectat şi local la adresa IP şi portul specificate.

 

Principalele metode sunt :

 

-         public InputStream getInputStream() throws IOException returnează un flux de intrare ataşat socket-ului curent. Acest flux este utilizat pentru a citi de la socket-ul respectiv;

-         public OutputStream getOutputStream() throws IOException returnează un flux de ieşire ataşat socket-ului curent. Acest flux este utilizat pentru a scrie la socket-ul respectiv;

-         public synchronized void close() throws IOException închide socket-ul curent;

-         public InetAddress getInetAddress() returnează adresa la distanţă la care este conectat socket-ul curent;

-         public InetAddress getInetAddress() returnează adresa locală la care este conectat socket-ul curent;

-         public int getPort() returnează portul la distanţă la care este conectat socket-ul curent;

-         public int getLocalPort() returnează portul local la care este conectat socket-ul curent.

 

Clasa ServerSocket are trei constructori dintre care cel mai utilizat este

 

            public ServerSocket(int port) throws IOException

 

care crează un soclu server la portul specificat. Cea mai importantă metodă a clasei este metoda

 

            public Socket accept()

 

care permite soclului server să aştepte cereri de conexiune de la clienţi şi apoi să le accepte. Soclul (Socket-ul) returnat de metodă este soclul asociat clientului care a făcut cererea; prin intermediul acestuia se va desfăşura comunicarea.

            Următoarele două programe, client şi server, sunt o primă exemplificare a celor spuse mai sus.

            Programul server aşteaptă o cerere de conexiune din partea unui client. Instanţa ServerSocket este folosită pentru aceasta. După ce acceptă conexiunea (desemnând aceasta prin crearea unui soclu pereche pentru cel al clientului) îi asociază un flux de date de tip ieşiere, DataOutputStream, prin intermediul căruia va transmite date către client. Aceste date sunt preluate dintr-un fişier specificat ca argument în linia de comandă la lansarea în execuţie a severului.

 

import java.net.*;

import java.io.*;

public class Server1

{

      public static void main (String[] args)

      {    

            try{

                  ServerSocket ss=new ServerSocket(15812);

                  Socket client=ss.accept();

                  DataOutputStream out = new DataOutputStream(client.getOutputStream());

                  BufferedReader br = new BufferedReader(new FileReader(args[0]));

                  String line="";

                  while ((line=br.readLine())!=null){

                        System.out.println(line);

                        out.writeUTF(line);

                  }

                  out.writeUTF("Serverul a terminat !");

                  client.close();

            }catch (Exception e){System.out.println("Eroare : "+e);}

      }

}

 

            Programul client îşi declară un soclu şi îl asociază serverului aflat pe calculatorul args[0] şi la portul 15812 (acelaşi cu cel declarat în programul server). Implicit sistemul lansează cererea de conexiune către acest calculator. Soclului i se asociază un flux de intrare, DataInputStream, prin care vor fi preluate date de la server. Datele preluate vor fi scrise de către client într-un fişier al cărui nume este specificat în args[1].

 

import java.net.*;

import java.io.*;

 

public class Client1

{

      public static void main (String[] args)

      {    

            try{

                  Socket s=new Socket(args[0],15812);

                  DataInputStream intrare = new DataInputStream(s.getInputStream());

                  BufferedWriter bw=new BufferedWriter(new FileWriter(args[1]));

                  boolean gata=false;

                  String line="";

                                   

                  while (!gata){

                        line=intrare.readUTF();

                        System.out.println(line);

                        bw.write(line);

                        bw.newLine();

                        if ((line.startsWith("Serverul a terminat !"))) gata=true;

                  }

                  s.close();

                  bw.close();

            }catch(Exception e){System.out.println("Eroare clienr: "+e);}

      }

}

 

            Programul serever pe care îl prezentăm în continuare este de tip multifir. El ia în consideraţie faptul că la server pot sosi cereri de transfer date de la mai mulţi clienţi în acelaşi timp.

 

import java.io.*;

import java.net.*;

import java.util.*;

 

public class Server2

{

      protected final int portServer=15000;

      protected ServerSocket socketAscultare;

     

      public Server2(){

            try{

                  socketAscultare=new ServerSocket(portServer);

            }catch(BindException e1){

                  System.err.println("Exista un alt server pe portul "+portServer);

                  System.exit(1);

            }catch(IOException e2){

                   System.err.println("Eroare la crearea soclului de ascultare ");

                   System.exit(1);

            }

      }

      public Socket asculta(){

            Socket socket=null;

            try{

                  socket=socketAscultare.accept();

            }

            catch(IOException e){

                  System.err.println("Eroare la receptia cererii de conexiune");

                  System.exit(1);

            }

            System.out.println("Client acceptat !");

            return socket;

      }

     

      public static void main (String[] args)

      {

            Server2 server=new Server2();

            try{

                  while (true){

                        Socket socketConexiune=server.asculta();

                        new Thread(new Conexiune(socketConexiune));

                  }

            }

            finally{

                  try{

                        server.socketAscultare.close();

                  }

                  catch(IOException e){

                        System.err.println("Eroare la inchiderea soclului de ascultare");

                  }

            }

      }

}

class Conexiune extends Thread

{

      protected Socket socketConexiune;

      protected String numefisServer;

     

      public Conexiune(Socket socketConexiune){

            this.socketConexiune=socketConexiune;

            this.start();

      }

      public void run(){

            try{

                  BufferedReader fluxDeLaClient=

                        new BufferedReader(new InputStreamReader(socketConexiune.getInputStream()));

                  numefisServer=fluxDeLaClient.readLine();

                  Vector vector=new Vector(100,10);

                  synchronized(numefisServer){

                        File file=new File(numefisServer);

                        if (file.isDirectory()){

                              vector.addElement(

                                    new String(numefisServer+" este un director cu continutul :"+"\n"));

                              String[] rezumat=file.list();

                              for(int i=0;i<rezumat.length;i++)

                                    vector.addElement(rezumat[i]);

                        }

                        else if (file.isFile()){

                              vector.addElement(

                                    new String(numefisServer+" este un fisier cu continutul :"+"\n"));

                              String linie="";

                              BufferedReader fisInput=

                                    new BufferedReader(new FileReader(file));

                              while((linie=fisInput.readLine())!=null)

                                    vector.addElement(linie);

                              fisInput.close();

                        }

                        else vector.addElement(

                              new String(numefisServer+" nu este nume de director sau fisier"));

                  }

                  DataOutputStream fluxSpreClient=

                        new DataOutputStream(socketConexiune.getOutputStream());

                  for(int i=0;i<vector.size();i++)

                        fluxSpreClient.writeUTF((String)vector.elementAt(i));

                  fluxSpreClient.writeUTF("Gata");

                  fluxSpreClient.close();

                  fluxDeLaClient.close();

                  System.out.println("Gata client!");

            }

            catch(IOException e){

                  System.err.println("Eroare de comunicare ");

            }

            finally{

                  try{

                        socketConexiune.close();

                  }catch(IOException e){

                        System.err.println("Eroare la inchidere conexiune ");

                  }

            }

      }

}

 

            În constructorul clasei Server2 se instanţiază soclul socketAscultare unde aplicaţia “aşteaptă” cereri de conexiune. În ciclul infinit din metoda main aplicaţia aşteaptă cereri de transfer date de la clienţi. Acceptarea unei astfel de cereri este urmată de instanţierea unui nou fir de execuţie al clasei Conexiune. Această clasă conţine metodele care se ocupă de transferul de date cu clientul. Se poate transmite către client conţinutul unui fişier sau al unui director.

 

 

4.2.3 Comunicarea fără conexiune : clasele DatagramSocket şi DatagramPacket

 

            În pachetul java.net sunt prevăzute două clase care permit comunicarea bazată pe protocolul UDP. Acestea sunt DatagramSocket şi DatagramPacket.

            Instanţele clasei DatagramPacket sunt pachetele datagramă care se transmit între server şi client. Clasa are doi constructori.

 

            public DatagramPacket(byte buf[],int length) instanţiază un pachet datagramă destinat recepţionării pachetelor de lungime length în zona buf. Este necesar ca length să fie cel puţin egal cu buf.length.

 

            public DatagramPacket(byte buf[], int length, InetAddress adr, int port) instanţiază un pachet datagramă destinat trimiterii pachetelor de lungime length la calculatorul şi portul specificate.

 

            Clasa conţine metode care permit preluarea, din pachetul datagramă recepţionat, a unor informaţii precum : adresa IP (metoda getAddress()) şi portul (metoda getPort()) corespunzătoare calculatorului de la care s-a făcut recepţia cât şi partea de date a datagramei (metoda getData()). Metodele setAddress(InetAddress adr), setPort(int port) şi setData(byte buf[]) permit setarea elementelor de mai sus în pachetul datagramă ce urmează să fie trimis.

            Obiectele clasei DatagramSocket sunt soclurile prin intermediul cărora se face transmisia în ambele sensuri. Clasa are doi constructori.

 

            public DatagramSocket(int port)  throws SocketException - care crează un soclu datagramă şi îl asociază portului specificat de pe calculatorul local.

 

            public DatagramSocket(int port, InetAddress adr) throws SocketException - care deschide un soclu datagramă şi îl asociază portului şi calculatorului local specificate.

 

            Trei metode ale clasei sunt mai importante :

 

            public void send(DatagramPocket dgp) throws IOEception - transmite de la soclul curent un pachet datagramă. Pachetul trebuie pregătit în prealabil astfel încât să conţină datele transmise, adresa IP şi portul calculatorului unde trebuie să ajungă datagrama.

 

            public synchronized void receive(DatagramPacket dgp) throws IOException - care recepţionează un pachet datagramă la soclul curent. Recepţia se face în pachetu specificat. Dacă datagrama recepţionată are lungimea mai mare decât lungimea tamponului destinat recepţiei atunci datagrama va fi trunchiată. Metoda blochează firul de execuţie curent până când se recepţionează o datagramă.

 

            public void close() - închide soclul datagramă curent.

 

            Aplicaţia client-server care urmează exemplifică comunicaţia fără conexiune.

            Într-un ciclu infinit, sereverul aşteaptă cereri de transmisie date de la posibili clienţi (apelul metodei receive()). Pentru fiecare nou client se instanţiază un fir de execuţie al clasei DatagramServer precizându-se adresa şi portul acestuia (preluate din pachetul datagramă recepţionat). Clasa DatagramServer implementează transmisia datelor (conţinutul unui fişier text) prin protocolul UDP. În metoda run()se instanţiază câte un pachet datagramă pentru fiecare linie din fişier specificându-se în declaraţie adresa şi portul clientului pentru care se face transmisia.

 

import java.io.*;

import java.net.*;

 

public class DatagramServer implements Runnable

{

      private DatagramSocket dgSocket;

      private InetAddress inetaddressClient;

      private int portClient;

      private int dimbuf;

      private String filename;

     

      public DatagramServer(int dimbuf,DatagramSocket dgSocket,DatagramPacket dgp){

            this.dimbuf=dimbuf;

            this.inetaddressClient=dgp.getAddress();

            this.portClient=dgp.getPort();

            this.dgSocket=dgSocket;

            this.filename=new String(dgp.getData());

      }

     

      public void run(){

            byte[] buff=new byte[dimbuf];

            try{

                  synchronized(filename){

                        FileInputStream input=new FileInputStream(filename);

                        while (input.read(buff)!=-1){

                              DatagramPacket dgpSend=new DatagramPacket(buff,buff.length,inetaddressClient,portClient);

                              dgSocket.send(dgpSend);

                        }

                        buff=(new String("GATA")).getBytes();

                        DatagramPacket dgpSend=new DatagramPacket(buff,4,inetaddressClient,portClient);

                        dgSocket.send(dgpSend);

                        dgSocket.close();

                        input.close();

                  }

            }

            catch(IOException e){e.printStackTrace();}

      }

      public static void main (String[] args)

      {

            if (args.length != 1){

                  System.out.println("Utilizare : DatagramServer <numar port server>");

                  return;

            }

            int dimbuf=256;

            byte[] buff=new byte[dimbuf];

            DatagramPacket dgpReceive=new DatagramPacket(buff,dimbuf);

            try{

                  DatagramSocket dgSocket=new DatagramSocket(Integer.parseInt(args[0]));           

                  while (true){

                        dgSocket.receive(dgpReceive);

                        (new Thread(new DatagramServer(dimbuf,dgSocket,dgpReceive))).start();

                  }

            }catch(IOException e1){e1.printStackTrace();}

      }

}

 

            Partea de client este implementată de clasa DatagramClient. În constructorul clasei se instanţiază un soclu datagramă care va fi folosit pentru transmisia şi recepţia datagramelor.

            Metoda conexiune() iniţiază transferul de date cu serverul trimiţându-i acestuia un singur pachet datagramă în care este trecut numele fişierului de pe server al cărui conţinut se doreşte a fi recepţionat. În metoda receptie(), prin intermediul aceluiaşi pachet datagramă, se recepţionează toate liniile acestui fişier. De fiecare dată, din datagramă este preluată linia curentă din fişier şi scrisă într-un fişier local al clientului.

 

import java.io.*;

import java.net.*;

 

// Program client care preia de la server un fisier text.

// Se foloseste protocolul UDP.

 

public class DatagramClient

{

      protected int portServer, dimbuff;

      protected InetAddress inetaddressServer;

      protected DatagramSocket dgSocket=null;

      protected String numefisServer;

 

      public DatagramClient(String numeServer, int portServer, int dimbuff, String numefis){

            this.portServer=portServer;

            this.dimbuff=dimbuff;

            this.numefisServer=new String(numefis);

            try{

                  inetaddressServer=InetAddress.getByName(numeServer);

                  dgSocket = new DatagramSocket();

            }

            catch(IOException e){e.printStackTrace();}

      }

     

      public void conexiune(){

            byte[] buff=new byte[dimbuff];

            buff=(new String(numefisServer)).getBytes();

            DatagramPacket dgpSend = new DatagramPacket(buff,buff.length,inetaddressServer,portServer);

            try{

                  dgSocket.send(dgpSend);

            }catch(IOException e){e.printStackTrace();}

      }

      public void receptie(){

            byte[] buff=new byte[dimbuff];

            DatagramPacket dgpReceive=new DatagramPacket(buff,dimbuff);

            boolean ok=true;

            try{       

                  BufferedWriter output=

                        new  BufferedWriter(new FileWriter("myfis.txt"));

                  while (ok){

                        dgSocket.receive(dgpReceive);

                        String linie=new String(dgpReceive.getData());

                        if (!linie.startsWith("GATA")){

                              output.write(linie);

                              output.newLine();

                              System.out.println(linie);

                        }

                        else ok=false;

                  }

                  dgSocket.close();

                  output.close();

            }catch(IOException e){e.printStackTrace();}

      }

      public static void main (String[] args)

      {

            if(args.length != 3){

                  System.out.println("DatagramClient nume_Server port_Server nume_fisier_Server");

                  return;

            }

            DatagramClient client=new DatagramClient(args[0],Integer.parseInt(args[1]),256,args[2]);

            client.conexiune();

            client.receptie();

      }

}