C# și Java sunt două limbaje de programare care dezvoltă limbajul de programare C++ , cu o sintaxă care moștenește în mare măsură sintaxa C++ și create în multe privințe într-un mediu competitiv și, ca urmare, au anumite asemănări , precum și având o serie de diferente .
Limbile C# și Java au apărut în momente diferite. Limbajul Java a fost creat cu mult înainte de apariția C#. Oak Java a fost dezvoltat de Sun Microsystems în 1990, iar în 1995 a fost lansată prima versiune beta a Java. Crearea C# a fost anunțată în 2000, iar în 2002 a fost lansată prima versiune a platformei .NET pentru a suporta C#. Astfel, dacă Java a fost creat bazându-se mai mult pe experiența limbajelor Objective C și C, atunci pentru C#, C++ și Java în sine au fost un astfel de suport [1] . Și, în ciuda numelui său, C# s-a dovedit a fi mai aproape de Java decât de C++ [2] [3] .
Din punctul de vedere al dezvoltatorului, Java și C# sunt foarte asemănătoare. Ambele limbi sunt puternic tipizate, limbaje obiect. Ambele au încorporat o mare parte din sintaxa C++, dar spre deosebire de C++, sunt mai ușor de învățat pentru începători. Ambii au împrumutat de la C un set de cuvinte cheie de bază și simboluri de serviciu, inclusiv acolade pentru evidențierea blocurilor. Ambele limbi se bazează pe colectarea gunoiului . Ambele limbi sunt însoțite de colecții bogate de biblioteci. Dar limbile au și propriile lor caracteristici și diferențe, puncte forte și puncte slabe. C# a luat în considerare multe dintre deficiențele Java și le-a corectat în implementarea sa [4] . Dar Java nu stă pe loc, dezvoltându-se în paralel cu C#.
Kik Redek de la Microsoft consideră că C# este un limbaj mai complex decât Java [1] . În opinia sa, „ Java a fost construit pentru a împiedica un dezvoltator să se împuște în picior” și „C# a fost construit pentru a oferi dezvoltatorului o armă, dar să lase siguranța activată” (în engleză „C# a fost construit pentru a oferi dezvoltatorului o armă pistol, dar lăsați siguranța activată" ).
Diferențele sintactice sunt de asemenea suficiente.
Sintaxă | Java | C# |
---|---|---|
Importați nume statice ( import static) |
vă permite să importați separat unele sau toate metodele și variabilele statice ale unei clase și să folosiți numele lor fără calificare în modulul de import | De la C# 6.0 acest lucru a fost introdus (de exemplu ( using static System.Math)). |
declarație switch | Argumentul instrucțiunii switch trebuie să fie fie un număr întreg, fie un tip enumerat. Începând cu Java 7, a devenit posibilă utilizarea literalelor șir într-o instrucțiune switch, iar această distincție față de C# a fost eliminată [2] . | Sunt acceptate atât tipurile constante, cât și tipurile de șir. C# 7 a introdus suport pentru tipurile de referință și null. De asemenea, este posibil să specificați condiții suplimentare pentru un bloc folosind casecuvântul cheie when[5] . Spre deosebire de Java, nu există o tranziție directă la următorul bloc case. Pentru a trece la blocul următor case, trebuie să utilizați instrucțiunea goto [2] . |
Goto Jump declarație | utilizarea goto a fost abandonată în mod deliberat, cu toate acestea, există un mecanism care vă permite să ieșiți din bucla exterioară din cea imbricată prin marcarea acesteia cu eticheta și folosind operatorii break, continueîmpreună cu eticheta ( continue <метка>;) | goto este păstrat, utilizarea sa obișnuită este de a transfera controlul către diferite etichete casedin instrucțiune switchși de a ieși din bucla imbricată |
constante | nu există constante ca atare, se folosesc în schimb variabile statice de clasă cu un modificator final - efectul utilizării lor este exact același | concept separat de constantă tipizată numită și cuvânt cheieconst |
Precizie în virgulă flotantă | Java conține constructul strictfp , care garantează aceleași rezultate pentru operațiunile cu virgulă mobilă pe toate platformele. | C# se bazează pe implementare, nu există nicio garanție a exact aceleași rezultate ale calculelor. |
Dezactivarea controalelor | În Java, toate verificările dinamice sunt activate/dezactivate numai la nivel de pachet | C# conține constructele și checkedpentru a uncheckedactiva sau dezactiva verificarea dinamică a depășirii aritmetice la nivel local . |
Ambele limbaje implementează același model pentru lucrul cu date dinamice: obiectele sunt create dinamic folosind constructul new, runtime monitorizează referințe la ele, iar colectorul de gunoi curăță periodic memoria de obiectele care nu sunt referite. Pentru a optimiza colectarea gunoiului, specificațiile limbajelor și mediilor de rulare nu conțin restricții privind durata de viață a unui obiect după ce ultima referință la acesta a fost ștearsă - colectorul funcționează independent de execuția programului, astfel încât distrugerea efectivă a obiectul poate apărea în orice moment după ce ultima referință este ștearsă înainte ca programul să se încheie. În realitate, colectorii de gunoi optimizează execuția în așa fel încât să asigure un consum acceptabil de memorie cu o încetinire minimă a programelor.
Atât Java, cât și C# au referințe la obiecte puternice și slabe . Ambele limbi acceptă metode de finalizare . Datorită incertitudinii când un obiect este șters, finalizatoarele nu pot fi folosite pentru a elibera resursele de sistem ocupate de obiect, ceea ce vă obligă să creați metode suplimentare pentru a „curăța” obiectul și a le apela în mod explicit.
C# are o interfață în biblioteca standard IDisposableși o construcție specială usingpentru a se asigura că metoda de curățare este apelată la timp:
// DisposableClass implementează interfața IDisposable și descrie metoda acesteia Dispose class DisposableClass : IDisposable { public void Dispose () { // ... Resursele ocupate de instanță sunt eliberate aici } } folosind ( DisposableClass obj = new DisposableClass (...)) { // ... Cod care folosește obiectul obj } // ... Aici, metoda Dispose este garantată a fi apelată deja pe obiectul objNu există o astfel de construcție în Java și curățarea obiectelor se poate face doar manual:
class AnyClass { void clear () { // ... Clear codul merge aici } } AnyClass obj = nou AnyClass (...); încercați { // ... cod folosind obj } în final { obj . clar (); // - un apel explicit la metoda de curățare a obiectului după ce utilizarea sa este completă }Java 7 a adăugat un construct „try-with-resources” pentru a oferi curățare automată exact în același mod ca C#:
încercați ( BufferedReader br = new BufferedReader ( new FileReader ( cale ))) { return br . readLine (); }Când blocul try iese, toate obiectele cărora li s-a atribuit o valoare în antetul său (parantezele dinaintea blocului de instrucțiuni) vor fi șterse. O condiție prealabilă este ca clasele acestor obiecte să implementeze interfața de sistem java.lang.AutoCloseable.
Java vă permite să înregistrați un ascultător care va primi mesaje atunci când o referință este colectată de gunoi, ceea ce îmbunătățește performanța WeakHashMap .
C# (mai precis, common language runtime) vă permite să anulați execuția unui finalizator pentru un anumit obiect printr-o metodă GC.SuppressFinalize(obj)(de exemplu, o conexiune SQL pe un flux de fișiere). Acest lucru este util deoarece finalizarea este considerată o operațiune de colectare a gunoiului relativ costisitoare, iar un obiect cu un finalizator „trăiește” mai mult.
Ambele limbaje sunt orientate pe obiecte , cu o sintaxă moștenită din C++, dar reproiectată semnificativ. Codul și datele pot fi descrise numai în cadrul claselor.
ÎncapsulareÎn Java, modificatorul protejat din declarație, pe lângă accesul din clasele descendente, permite accesul din toate clasele din același pachet ca și clasa proprietarului.
În C#, pentru obiectele care ar trebui să fie vizibile în ansamblu (un analog aproximativ al pachetului Java), a fost introdus un modificator intern separat (un analog implicit în Java) și protejat își păstrează sensul inițial, preluat din C ++ - acces numai din clasele descendente. Este permis să combinați intern și protejat - apoi obțineți zona de acces corespunzătoare pentru protejat în Java.
Clasele interioareAmbele limbi vă permit să definiți o clasă în cadrul unei clase.
În Java, clasele interne sunt folosite pentru a emula închiderile. Clasele interne Java au acces la membrii non-statici ai clasei părinte, adică „știi despre asta”; în plus, în cadrul metodelor, puteți defini clase locale care au acces de citire la variabilele locale și clase locale fără nume (anonime), care vă permit de fapt să creați instanțe de obiecte și interfețe care suprascriu metodele clasei dvs., direct la locul respectiv. a folosirii lor. Gestionarea evenimentelor poate fi construită pe acest mecanism în programele Java (un eveniment generează un apel la o metodă care este abstractă în clasa de gestionare originală; acolo unde este nevoie de un handler de evenimente specific, programatorul creează o instanță a unei clase anonime locale - succesorul a clasei de handler de bază și o folosește direct) . Acest lucru elimină necesitatea unui tip special și a unui suport sintactic pentru evenimente, dar codul real care creează manipulatorii este ceva mai complex de înțeles. În special, domeniile variabile devin mai complexe .
C# are închideri și lambda. Abordarea C# este mai asemănătoare cu C++: clasele interne în C# au acces numai la membrii statici ai clasei exterioare, iar pentru a accesa membrii nestatici, trebuie să specificați în mod explicit o instanță a clasei exterioare. Clasele interne locale nu sunt acceptate în C#.
Expresiile Lambda au apărut și în Java începând cu versiunea 8 .
MetodeÎn ambele limbi, metodele sunt definite prin funcții de clasă. Corpul metodei este situat în interiorul declarației de clasă. Sunt acceptate metode statice, metode abstracte . C# are, de asemenea, o implementare explicită a metodelor de interfață, care permite unei clase să implementeze metode de interfață separat de propriile sale metode, sau să ofere implementări diferite ale metodelor cu același nume care aparțin a două interfețe diferite.
Java 8 a introdus instrucțiunea implicită, care vă permite să definiți implementarea „implicit” a metodelor de interfață. Astfel, clasa care implementează interfața scapă de obligația de a implementa metode implicite, dar le poate suprascrie.
O caracteristică similară a apărut în C # 9.0 - în cadrul descrierii interfeței, dezvoltatorul poate specifica corpuri pentru orice metodă. La fel ca în Java, atunci când implementează o interfață, dezvoltatorul poate suprascrie implementarea implicită, dar nu este obligat să facă acest lucru.
În Java, tipurile primitive ( byte, int, double, float, booleanetc.) sunt transmise după valoare, iar pentru celelalte tipuri (obiect), o referință la obiect este transmisă după valoare.
În C#, pe lângă tipurile primitive sunt transmise prin valoarea structurii ( struct) (așa-numitele tipuri de valoare), alte tipuri sunt transmise prin referință (așa-numitele tipuri de referință). refC# acceptă, de asemenea, descrierea explicită a trecerii parametrilor prin referință ( , inși cuvintele cheie out). Când se utilizează out, compilatorul controlează dacă valoarea este prezentă în metoda de atribuire. De asemenea, este acceptat să returneze valori din metode prin referință folosind constructul ref typename.
C# permite ca metodelor să li se dea același nume ca și numele clasei, creând astfel un constructor de clasă [6] .(În Java, un programator poate defini și un constructor care va fi de fapt o metodă) [4] .
C# acceptă mai multe subtipuri speciale de sintaxă atunci când descrie corpurile metodei:
- blocuri iteratoare: metode care returnează IEnumerable<T>sau IEnumerator<T>pot descrie în mod imperativ secvența valorilor returnate folosind constructele yield returnși yield break.
- metode asincrone: metodele care returnează Task/ ValueTask/ Task<T>/ ValueTask<T>și sunt marcate cu cuvântul cheie asyncpot folosi constructul await în corpurile lor atunci când apelează alte metode care returnează Task/ ValueTask/ Task<T>/ ValueTask<T>. Acest lucru vă permite să implementați multitasking cooperativ, deoarece. constructul await întrerupe execuția metodei curente până când valoarea cerută este gata și transferă controlul către planificator, care poate începe să execute următoarea sarcină gata fără a transfera controlul către nucleul OS.
Începând cu C# 8.0, puteți combina ambele caracteristici prin generarea de iteratoare asincrone - o implementare a interfețelor IAsyncEnumerable<T>/ IAsyncEnumerator<T>folosind constructele await, yield returnși yield break.
Virtualitatea metodelorC# copiază conceptul de metode virtuale C++ : o metodă virtuală trebuie declarată explicit cu cuvântul cheie virtual, alte metode nu sunt virtuale. Această declarație selectivă a metodelor virtuale a fost introdusă în C#, deoarece declararea tuturor metodelor virtuale poate încetini foarte mult execuția [7] . În plus, C# necesită o declarație explicită a unei înlocuiri a unei metode virtuale într-o clasă derivată cu cuvântul cheie override. Dacă doriți să ascundeți (ascundeți) o metodă virtuală, adică să introduceți o nouă metodă cu același nume și semnătură, trebuie să specificați un cuvânt cheie new(în lipsa căruia compilatorul emite un avertisment). Este interzisă ascunderea (obscurei) metodelor abstracte. Declararea unei metode de suprascriere cu cuvântul cheie sealedîmpiedică suprascrierea metodei de înlocuire în clasele descendente, dar vă permite totuși să o ascundeți.
În Java, dimpotrivă, toate metodele publice, cu excepția celor statice, sunt virtuale și este imposibil să suprascrieți o metodă, astfel încât mecanismul de virtualitate să nu pornească. Metoda suprascrie întotdeauna virtual metoda clasei de bază cu același nume și semnătură, dacă există. Cuvântul cheie finalvă permite să împiedicați crearea unei metode cu aceeași semnătură în clasele derivate.
Abordarea Java este mai simplă din punct de vedere sintactic și asigură că metoda clasei căreia îi aparține obiectul este întotdeauna invocată. Pe de altă parte, virtualitatea nu este întotdeauna necesară, iar suprasarcina de apelare a metodelor virtuale este oarecum mai mare, deoarece aceste apeluri de obicei nu trec prin substituție inline și necesită acces suplimentar la tabelul de metode virtuale (deși unele implementări ale JVM-ului, inclusiv implementarea Sun, implementează înlocuirea inline a celor mai frecvent numite metode virtuale).
Virtualitatea tuturor metodelor este potențial nesigură: dacă un programator declară în mod eronat o metodă care se află deja în clasa de bază, neavând intenția de a o depăși, dar pur și simplu nefiind atent la faptul că o astfel de metodă există deja, atunci noua metoda va suprascrie metoda cu același nume din clasa de bază, deși aceasta nu este intenția dezvoltatorului. În C#, este posibilă și o eroare similară, dar compilatorul va emite un avertisment că metoda de suprascrie este declarată fără newși override. Java 5 a introdus un mecanism similar - dacă o metodă suprascrie o metodă virtuală a unei clase strămoși, compilatorul emite un avertisment; Pentru a preveni emiterea unui avertisment, metoda de suprascriere trebuie adnotată cu adnotarea „@Override”.
Ambele limbi susțin ideea de tipuri primitive (care în C# sunt un subset de tipuri de valori - tipuri de valori ), și ambele pentru traducerea tipurilor primitive în tipuri de obiecte oferă „boxing” lor automat în obiecte (boxing) și „unboxing” (unboxing) (în Java - începând cu versiunea 5). În C#, tipurile primitive pot fi denumite obiecte, iar acesta este unul dintre motivele pentru care C# este popular. În Java, tipurile primitive și tipurile de obiecte sunt separate, clasele wrapper sunt folosite pentru a face referire la tipurile primitive ca obiecte (de exemplu, wrapper-ul Integer pentru tipul int), acest lucru provoacă nemulțumire cu mulți dezvoltatori Java [8] [9] .
Există mai multe tipuri primitive în C# decât în Java, datorită tipurilor întregi nesemnate (nesemnate), care sunt împerecheate cu toate cele semnate și a unui tip special decimalpentru calcule cu virgulă fixă de înaltă precizie (în Java, clasele java.math.BigIntegerși servesc pentru aceasta java.math.BigDecimal).
Java a abandonat majoritatea tipurilor nesemnate pentru a simplifica limbajul. Una dintre problemele binecunoscute cu astfel de tipuri este dificultatea de a determina tipul rezultatului operațiilor aritmetice pe două argumente, dintre care unul este semnat și celălalt nesemnat. Indiferent de regulile pe care le adoptă limbajul cu privire la astfel de operații, în unele situații va duce la erori (de exemplu, în C++, o operație pe o valoare cu semn și fără semn dă un rezultat fără semn; ca urmare, când se numără cu 16-). numere de biți, expresia „40000 / (-4 )” va da ca rezultat nu -10000, ci 55536). Totuși, acest refuz își creează propriile probleme; deoarece o parte semnificativă a datelor tehnice utilizate la un nivel scăzut (de exemplu, diverse date de serviciu transmise de hardware și returnate de funcțiile API ale sistemului de operare) sunt de tip întreg nesemnat, iar absența unor astfel de tipuri duce la necesitatea pentru a efectua operațiuni nesigure de conversie a datelor și, în unele cazuri - înlocuiți utilizarea aritmeticii simple fără semn cu combinații neevidente de operații pe biți.
Structuri (înregistrări)C# vă permite să creați tipuri de valori personalizate folosind struct. Aceasta este o moștenire directă a limbajului C++, pe care creatorii Java au abandonat-o în mod deliberat. Spre deosebire de instanțe de clasă, instanțele de tip valoare sunt create nu pe heap , ci pe stiva de apeluri sau ca parte a instanței obiectului în care sunt declarate, ceea ce în unele cazuri îmbunătățește performanța codului. Din punctul de vedere al unui programator, ele sunt asemănătoare claselor, dar cu câteva limitări: nu pot avea un constructor explicit fără parametri (dar pot avea un constructor cu parametri), nu pot fi moștenite din [10] și nu pot moșteni explicit. din alte tipuri (intotdeauna implicit) sunt moștenite de la clasă System.ValueType), dar pot implementa interfețe. În plus, valorile tipurilor de structuri suportă logica atribuirii unei valori (adică atribuirea valorii unei variabile alteia nu copiază referința la același obiect, ci copiază valorile câmpului unei structuri în o alta). Începând cu versiunea 1.6, Java are și capacitatea de a crea obiecte pe stivă, dar acest lucru se întâmplă automat fără intervenția utilizatorului.
În Java, pentru a preveni moștenirea unei clase, aceasta poate fi declarată finală final, obținându-se astfel un analog parțial al construcției struct(copia după valoare oricum nu va fi suportată). sealedC# folosește modificatorul [11] în același scop .
Tipuri enumerateTipurile enumerate în C# sunt derivate din tipurile integrale primitive. O valoare validă a unui tip enumerat este orice valoare a primitivei sale subiacente, deși atribuirea sa poate necesita o distribuție explicită . Acest lucru permite ca valorile de tip enumerate să fie combinate cu o operațiune „sau” pe biți, dacă sunt indicatori de biți.
În Java, tipurile enumerate sunt clase, iar valorile lor sunt, respectiv, obiecte. Un tip de enumerare poate avea metode, implementa interfețe. Singurele valori de tip valide sunt cele enumerate în declarație. Combinarea lor împreună ca steaguri necesită un obiect special de set de enumerare. Este posibil să se definească diferite implementări de metodă pentru fiecare valoare.
Matrice și colecțiiArrayurile și colecțiile au primit expresie și în sintaxa ambelor limbi, datorită unui tip special de buclă for (bucla peste o colecție, cunoscută și sub numele de buclă for foreach). În ambele limbi, un tablou este un obiect al clasei Array, dar în Java, nu implementează nicio interfață de colecție, deși este posibil să iterați peste matrice cu o buclă for(:). Ambele limbi au clase de colecție generice în biblioteca standard .
În Java, strict vorbind, doar tablourile unidimensionale pot fi declarate. O matrice multidimensională în Java este o matrice de matrice. C# are atât matrice multidimensionale adevărate, cât și matrice de matrice, care sunt denumite în mod obișnuit în C# matrice „jagged” sau „jagged”. Matricele multidimensionale sunt întotdeauna „dreptunghiulare” (în termeni 2D), în timp ce matricele de matrice pot stoca șiruri de lungimi diferite (din nou în 2D, la fel și în multidimensionale). Matricele multidimensionale accelerează accesul la memorie (pentru ei, indicatorul este dereferențiat o singură dată), în timp ce matricele zimțate sunt mai lente, dar economisesc memorie atunci când nu toate rândurile sunt pline. Matricele multidimensionale necesită un singur apel al operatorului pentru a le crea new, în timp ce matricele zimțate necesită alocarea explicită a memoriei într-o buclă pentru fiecare rând.
Tipuri parametrizate (generice)În ambele limbi, tipurile pot fi parametrizate, ceea ce acceptă paradigma de programare generică . Sintactic, definiția tipurilor este destul de apropiată - în ambele limbi este moștenită din șabloanele C ++, deși cu unele modificări.
Genericurile în Java sunt pur o construcție de limbaj și sunt implementate numai în compilator. Compilatorul înlocuiește toate tipurile generice cu limitele lor superioare și inserează distribuția adecvată acolo unde este utilizat tipul parametrizat. Rezultatul este un bytecode care nu conține referințe la tipuri generice și parametrii acestora. Această tehnică de implementare a tipurilor generice se numește ștergere de tip . Aceasta înseamnă că informațiile despre tipurile generice originale nu sunt disponibile în timpul rulării și impune unele restricții, cum ar fi incapacitatea de a crea instanțe noi de matrice din argumente de tip generic. Mediul de rulare Java nu este familiarizat cu sistemul de tip generic, ceea ce are ca rezultat noile implementări JVM care necesită doar actualizări minime pentru a funcționa cu noul format de clasă.
C# a mers pe direcția inversă. Suportul de genericitate a fost integrat în runtime virtuală, introdus pentru prima dată în .NET 2.0. Limbajul aici a devenit doar o interfață externă pentru accesarea acestor caracteristici ale mediului. Ca și în Java, compilatorul efectuează verificarea tipului static, dar în plus, JIT efectuează validarea timpului de încărcare . Informațiile de tip generic sunt pe deplin prezente în timpul execuției și permit suport complet pentru reflectarea tipului generic și crearea de noi implementări.
Abordarea Java necesită verificări suplimentare de rulare, nu garantează că clientul de cod va urma potrivirea tipului și nu oferă reflectare pentru tipurile generice. Java nu permite ca genericele să fie specializate în primitive (acest lucru se poate face numai prin încadrarea tipurilor primitive în clase), în timp ce C# oferă generice atât pentru tipurile de referință, cât și pentru tipurile de valori, inclusiv primitive. În schimb, Java sugerează folosirea tipurilor primitive încapsulate ca parametri (de exemplu , List<Integer>în loc List<int>de ), dar acest lucru vine cu costul alocării heap suplimentare. Atât în Java, cât și în C#, specializările de tip generic pe diferite tipuri de referință produc același cod [12] , dar pentru C#, runtime-ul generează în mod dinamic cod optimizat atunci când se specializează pe tipuri de valori (de ex. List<int>), ceea ce le permite să fie stocate și preluate din containere fără operațiuni pentru- și desfășurare.
Java cere programatorului să implementeze manual modelul de observator , deși oferă o cantitate de zahăr sintactic sub formă de clase anonime imbricate , ceea ce vă permite să definiți un corp de clasă și să îl instanțiați imediat la un moment dat din cod.
C# oferă suport extins pentru programarea bazată pe evenimente la nivel de limbaj, inclusiv delegați .NET , multicasting, sintaxă specială pentru setarea evenimentelor în clase, operațiuni pentru înregistrarea și anularea înregistrării gestionatorilor de evenimente, covarianța delegaților și metode anonime cu un set complet de semantică de închidere .
Închiderile sunt incluse în Java SE 8 [1] . Aceste închideri, ca și delegații în C#, au acces deplin la toate variabilele locale dintr-un anumit domeniu de aplicare, nu doar acces de citire la variabilele marcate cu un cuvânt final(ca și în cazul claselor imbricate anonime).
C# include supraîncărcarea operatorului și turnarea tipului specificat de utilizator , care sunt familiare programatorilor C++. C# îl acceptă cu unele restricții care asigură integritatea logică, care, atunci când este utilizat cu atenție, ajută la a face codul mai concis și mai lizibil. Java nu include supraîncărcarea operatorului pentru a evita abuzul și pentru a menține limbajul simplu [13] [14] [15] .
C# suportă conceptul de „proprietăți” - pseudo-câmpuri ale unei clase, la care accesul este asigurat cu control deplin prin crearea unor metode de preluare și setare a valorii câmpului. Descrierea proprietăților se realizează folosind construcțiile getși set. Nu există un astfel de concept în Java [16] (deși nu există restricții pentru a-l implementa folosind metode tradiționale).
C# include, de asemenea, așa-numitele indexare , care pot fi considerate ca un caz special de supraîncărcare a operatorului (similar cu supraîncărcarea operator[]în C++) sau proprietăți parametrizate. Un indexator este o proprietate numită this[], care poate avea unul sau mai mulți parametri (indici), iar indicii pot fi de orice tip. Acest lucru vă permite să creați clase ale căror instanțe se comportă ca matrice/Hartă:
myList [ 4 ] = 5 ; nume șir = xmlNode . Atribute [ „nume” ]; comenzi = customerMap [ theCustomer ];Utilizarea proprietăților este respinsă de unii programatori autorizați. În special, Jeffrey Richter scrie:
„Personal, nu-mi plac proprietățile și m-aș bucura dacă suportul lor ar fi eliminat din Microsoft .NET Framework și din limbajele de programare aferente. Motivul este că proprietățile arată ca câmpuri, dar sunt de fapt metode.” [17]
Conform stilului obișnuit de denumire C#, numele proprietăților sunt distincte vizual de câmpuri prin faptul că încep cu o literă mare.
C#, spre deosebire de Java, acceptă compilarea condiționată folosind directive de preprocesor . Are, de asemenea, un atribut Conditionalcare înseamnă că metoda specificată este apelată numai atunci când constanta de compilare dată este definită. În acest fel, puteți introduce în cod, de exemplu, verificări de aserțiune, care vor funcționa numai în versiunea de depanare atunci când constanta este definită DEBUG. În biblioteca standard .NET, acesta este Debug.Assert().
Versiunile Java 1.4 și ulterioare includ un verificator de ipoteze de rulare în limbaj. În plus, dacă constructele cu condiții constante pot fi extinse în timpul compilării. Există implementări de la terțe părți ale preprocesoarelor pentru Java, acestea fiind utilizate în principal în dezvoltarea de aplicații pentru dispozitive mobile.
Modulele externe în Java și C# sunt conectate într-un mod similar. Java folosește cuvântul cheie import, C# folosește cuvântul cheie using. Exemplu [18] :
// Exemplu Java import java.lang.System ; public class GlobalGreeting2 { public static void main ( String [] args ) { System . afară . println ( "Zdravo, zemjata!" ); } } | // Exemplu C# folosind System ; public class GlobalGreeting2 { public static void Main ( șir [] args ) { Consolă . WriteLine ( "Salut, monde!" ); } } |
Diferența esențială dintre importul în Java și utilizarea în C# este că C# folosește conceptul de spații de nume (namespace), care amintește de mecanismul C++ cu același nume [18] . Java folosește conceptul de pachete . Spațiile de nume nu au nimic de-a face cu modulele compilate (ansambluri sau asamblare în terminologia Microsoft). Mai multe ansambluri pot conține același spațiu de nume, iar un ansamblu poate declara mai multe spații de nume, nu neapărat imbricate. Modificatorii domeniului C# nu au nimic de-a face cu spațiile de nume. În Java, clasele declarate implicit în același pachet formează o singură unitate compilată. Modificatorul implicit al domeniului de aplicare (fără specificație explicită) restricționează domeniul de aplicare al câmpurilor și metodelor de clasă la pachet.
În Java, structura fișierelor sursă și a directoarelor unui pachet este în mod implicit legată de structura pachetului - un pachet corespunde unui director, subpachetele sale - subdirectoarele acestui director, fișierele sursă sunt localizate în directoare corespunzătoare pachetului sau subpachet în care sunt incluse. Astfel, arborele sursă urmează structura pachetului. În C#, locația unui fișier sursă nu are nimic de-a face cu spațiul său de nume.
Niciuna dintre opțiuni nu are o superioritate semnificativă în putere, doar mecanisme diferite sunt folosite pentru a rezolva ambiguitățile [18] .
În C#, clasele pot fi plasate în fișiere în mod arbitrar. Numele fișierului cod sursă nu are nimic de-a face cu numele claselor definite în acesta. Este permisă plasarea mai multor clase publice într-un singur fișier. Începând cu versiunea 2.0, C# vă permite, de asemenea, să împărțiți o clasă în două sau mai multe fișiere (cuvânt cheie partial). Ultima caracteristică este făcută pentru a separa codul care este scris de o persoană și codul care este generat. Este folosit, de exemplu, de instrumentele de construire a interfeței vizuale: partea clasei care conține câmpurile și metodele controlate de constructorul de interfață este separată într-un fișier separat.
În Java, fiecare fișier poate conține o singură clasă publică, iar Java necesită ca numele fișierului să fie același cu numele acelei clase, ceea ce elimină confuzia privind denumirea fișierului și a clasei. În plus, conform convenției de formatare a codului recomandate de Sun, dimensiunea unui fișier cu cod sursă nu trebuie să depășească 2000 de linii de cod , deoarece un fișier mai mare este mai dificil de analizat. O dimensiune mare a fișierului este, de asemenea, considerată un semn de design slab.
Ambele limbi suportă un mecanism de gestionare a excepțiilor care este proiectat sintactic exact în același mod: limbajul are un operator de aruncare a excepțiilor throwși un bloc de gestionare a excepțiilor try{}catch(){}finally{}care asigură interceptarea excepțiilor apărute în interiorul blocului, procesarea acestora, precum și ca executarea garantată a acţiunilor finale.
Java acceptă excepții verificate (verificate) : programatorul trebuie să specifice în mod explicit pentru fiecare metodă tipurile de excepții pe care metoda le poate arunca, aceste tipuri sunt listate în declarația metodei după cuvântul cheie throws. Dacă o metodă folosește metode care aruncă excepții verificate, trebuie fie să prindă în mod explicit toate aceste excepții, fie să le includă în propria sa declarație. Astfel, codul conține în mod explicit o listă de excepții care pot fi aruncate de fiecare metodă. Ierarhia tipului de excepție conține și două tipuri ( RuntimeExceptionși Error), ai căror descendenți nu sunt bifați și nu trebuie declarați. Acestea sunt rezervate pentru excepțiile de rulare care pot apărea oriunde sau care nu pot fi gestionate în mod normal de programator (cum ar fi erorile de rulare) și nu ar trebui declarate într-un throws.
C# nu acceptă excepțiile verificate. Absența lor este o alegere conștientă a dezvoltatorilor. Anders Hejlsberg , arhitectul șef al C#, consideră că în Java au fost într-o oarecare măsură un experiment și nu s-au justificat [2] .
Utilitatea excepțiilor verificate este discutabilă. Consultați articolul Gestionarea excepțiilor pentru detalii .
În general, mecanismele de programare paralelă în C# sunt similare cu cele oferite de Java, diferența constă în detaliile implementării. În ambele cazuri, există o clasă de bibliotecă Thread care implementează conceptul de „thread”. Java oferă două moduri de a crea fire native, fie prin extinderea clasei Thread, fie prin implementarea interfeței Runnable. În ambele cazuri, programatorul trebuie să definească o metodă run() moștenită (inclusă în interfață) care conține corpul firului de execuție - codul care va fi executat în acesta. C# folosește în schimb mecanismul delegat: pentru a crea un thread, se creează o instanță a clasei standard Thread, căreia i se trece un delegat ca parametru de constructor, care conține o metodă - corpul firului.
Ambele limbi au capacitatea de a crea un bloc de cod executabil sincron; în Java acest lucru se face cu operatorul synchronized(), în C# cu operatorul lock(). De asemenea, în Java este posibil să se declare metode sincrone utilizând modificatorul sincronizat din antetul unei declarații de metodă. Astfel de metode își blochează obiectul gazdă atunci când sunt executate (astfel, dintre metodele de clasă sincronizată, pentru aceeași instanță, doar una poate fi executată în același timp și doar într-un fir, restul va aștepta). O capacitate similară în .NET este implementată utilizând atributul de implementare a metodei MethodImplAttribute MethodImplOptions.Synchronized, dar spre deosebire de Java, această capacitate nu face parte oficial din limbajul C#.
C# are un operator lock(){} [3] care dobândește o blocare înainte de a intra într-un bloc și o eliberează atât la ieșire, cât și la aruncarea unei excepții. Omologul Java este sincronizat() {}.
C# 4.5 a introdus operatorii async și await [4] , precum și o nouă clasă Task care este mai eficientă decât Thread pentru sarcini scurte, concurente. Procesarea paralelă eficientă a tipurilor enumerate (containere enumerabile) este implementată pe același mecanism. [5]
În ambele limbi sunt disponibile și mijloace identice de sincronizare, bazate pe trimiterea și așteptarea unui semnal de la un fir în altul (altele). În Java, acestea sunt metodele notify(), notifyAll() și wait(), în C# - metodele Pulse(), PulseAll(), Wait() (cele trei metode sunt similare funcțional în perechi). Singura diferență este că în Java aceste metode (și, în consecință, funcționalitatea monitorului) sunt implementate în clasa Object, deci nu sunt necesare biblioteci suplimentare pentru sincronizare, în timp ce în C# aceste metode sunt implementate ca metode statice într-o clasă de bibliotecă separată Monitor (utilizat implicit de blocarea operatorului). În C#, biblioteca standard conține și câteva primitive de sincronizare suplimentare pentru execuția paralelă a firelor de execuție: mutexuri, semafore, cronometre de sincronizare. Începând cu versiunea 1.5, JDK SE a inclus pachetele java.util.concurrent, java.util.concurrent.atomic și java.util.concurrent.locks, care conțin un set cuprinzător de instrumente pentru implementarea calculului paralel.
Java Native Interface (JNI) permite programelor să apeleze funcții de nivel scăzut, specifice sistemului (cum ar fi bibliotecile winAPI) din Java. De regulă, JNI este folosit la scrierea driverelor. Când scrieți biblioteci JNI, un dezvoltator trebuie să folosească un API special oferit gratuit. Există, de asemenea, biblioteci specializate pentru interacțiunea Java cu COM.
Tehnologia Platform Invoke (P/Invoke) implementată în .NET vă permite să apelați cod extern din C#, pe care Microsoft îl numește negestionat . Prin atributele din metadate, programatorul poate controla cu precizie trecerea ( marshaling ) parametrilor și rezultatelor, evitând astfel necesitatea unui cod suplimentar de personalizare. P/Invoke oferă acces aproape complet la API-urile procedurale (cum ar fi Win32 sau POSIX ), dar nu oferă acces direct la bibliotecile de clasă C++.
.NET Framework oferă, de asemenea, o punte între .NET și COM , permițându-vă să accesați componentele COM ca și cum ar fi obiecte .NET native, ceea ce necesită un efort suplimentar de programare atunci când utilizați componente COM cu interfețe complexe non-triviale (de exemplu, în cazul trecerii structurilor printr-o matrice de octeți). În aceste cazuri, trebuie să recurgeți la cod nesigur (vezi mai jos) sau la alte soluții.
C# permite utilizarea limitată a indicatorilor , pe care designerii de limbaj le consideră adesea periculoase. Abordarea C# în acest sens este de a solicita cuvântul cheie unsafepe blocuri de cod sau metode care utilizează această caracteristică. Acest cuvânt cheie avertizează utilizatorii unui astfel de cod despre pericolul său potențial. De asemenea, necesită o opțiune de compilare explicită /unsafe, care este dezactivată în mod implicit. Un astfel de cod „nesigur” este folosit pentru a îmbunătăți interacțiunea cu API-ul negestionat și, uneori, pentru a îmbunătăți eficiența anumitor secțiuni ale codului.
De asemenea, C# permite programatorului să dezactiveze verificarea normală a tipului și alte caracteristici de siguranță CLR, permițând utilizarea variabilelor pointer atâta timp cât unsafe. Avantajul codului gestionat nesigur față de P/Invoke sau JNI este că permite programatorului să continue să lucreze într-un mediu familiar C# pentru a efectua sarcini care altfel ar necesita apelarea codului negestionat scris într-o altă limbă.
Există numeroase implementări JVM pentru aproape fiecare platformă de pe piață. JVM-ul este dezvoltat de corporații precum IBM , Sun Microsystems (din 2010 Oracle ), Bea și o serie de altele. Trebuie remarcat faptul că Sun (Oracle) își lansează JVM-ul atât sub propria licență [6] , cât și sub o licență GPLv2 modificată (prin așa-numita „Excepție Classpath”) [7] Arhivat 3 martie 2012. .
Java Web Start și appleturile oferă o modalitate convenabilă, ușoară și sigură de distribuire a aplicațiilor desktop, iar eficiența reprezentării sale bytecode , împreună cu tehnologiile de compresie agresive, cum ar fi pack200 , fac din Java un instrument de distribuție a aplicațiilor web cu lățime de bandă intensivă.
C# este, de asemenea, un standard multiplatform. Platforma sa principală este Windows , dar există implementări pentru alte platforme, dintre care cel mai semnificativ este proiectul Mono .
.NET este o platformă universală de dezvoltare open source întreținută de Microsoft și de comunitatea .NET de pe GitHub. Este multiplatformă (suporta Windows, macOS și Linux) și poate fi folosit pentru a construi aplicații pentru dispozitive, cloud și IoT.
ClickOnce oferă funcționalități similare cu Java Web Start, dar este disponibilă numai pentru clienții Windows. Internet Explorer pe Windows poate afișa elemente de interfață .NET Windows Forms , care oferă funcționalitate asemănătoare applet-ului, dar este limitat la un anumit browser.
Dezvoltarea acestor două limbaje, precum și API-urile, formatele binare și timpii de execuție ale acestora sunt gestionate diferit.
C# este definit de standardele ECMA și ISO , care definesc sintaxa limbii, formatul de modul executabil (cunoscut sub numele de CLI) și Biblioteca de clasă de bază (BCL). Standardele nu includ multe dintre noile biblioteci implementate de Microsoft pe lângă cadrul standard, cum ar fi biblioteci pentru baze de date, GUI și aplicații web ( Windows Forms , ASP.NET și ADO.NET ). Cu toate acestea, Microsoft a acceptat oficial să nu dea în judecată proiectele comunității pentru implementarea acestor biblioteci [8] (link inaccesibil) .
Până în prezent, nicio componentă a mediului Java nu a fost standardizată de Ecma , ISO , ANSI sau de orice altă organizație de standardizare terță parte. În timp ce Oracle își păstrează drepturi legale exclusive și nerestricționate de a modifica și licenția mărcile sale comerciale Java, Oracle participă în mod voluntar la un proces numit Java Community Process (JCP), care permite părților interesate să propună modificări la oricare dintre tehnologiile Java Oracle (limbaj, set de instrumente, API). ) prin consultări și grupuri de experți. Conform regulilor JCP, orice propunere de modificare a JDK , a mediului de rulare Java sau a specificației limbajului Java poate fi respinsă unilateral de Oracle, deoarece este necesar un vot „da” din partea Oracle pentru aprobarea acesteia. JCP solicită o taxă de membru de la participanții comerciali, în timp ce organizațiile non-profit și persoanele fizice pot participa gratuit.
În timp ce „Java” este o marcă comercială a Oracle (fostă Sun) și numai Oracle poate licenția numele „Java”, există numeroase proiecte gratuite care sunt parțial compatibile cu Oracle Java. De exemplu, GNU Classpath și GNU Compiler for Java (GCJ) oferă o bibliotecă de clasă gratuită și un compilator parțial compatibil cu versiunea curentă a Oracle Java [19] . La sfârșitul anului 2006, Sun a anunțat că tot codul sursă Java, cu excepția codului proprietar asupra căruia nu dețin drepturi, va fi lansat ca software gratuit până în martie 2007 sub o licență GPL modificată [20] . Oracle își distribuie în prezent HotSpot Virtual Machine și compilatorul Java sub GPL, dar în prezent nu există o licență gratuită pentru runtime Java standard [21] [22] . Deoarece Oracle își va păstra dreptul de proprietate asupra codului sursă Java, lansarea sub GPL nu va împiedica Oracle să distribuie versiuni non-free sau open-source ale Java sau să îl licențieze altora [23] .
C#, CLR și majoritatea bibliotecii de clase aferente sunt standardizate și pot fi implementate liber fără licență. Au fost deja implementate mai multe sisteme C# gratuite, inclusiv Mono și DotGNU . Proiectul Mono implementează, de asemenea, multe biblioteci Microsoft non-standard, învățând din materiale Microsoft, similare cu GNU Classpath și Java. Scopul proiectului Mono este de a evita încălcarea oricăror brevete sau drepturi de autor, iar proiectul este liber să fie distribuit și utilizat sub GPL [24] . Microsoft distribuie în prezent o versiune sursă partajată a runtime-ului său .NET pentru uz necomercial [25] .
Interpreții Java pot fi instalați prin copierea fișierelor și funcționează fără restricții pe Windows din cel puțin Windows 2000. Cadrul oficial C# trebuie instalat pe sistem ca administrator, anumite versiuni ale limbajului pot necesita o anumită versiune de Windows.
Java este construit pe o cultură mai deschisă, cu firme extrem de competitive în diferite domenii de funcționalitate. Majoritatea bibliotecilor suplimentare sunt disponibile sub licențe gratuite și open source. Sun încurajează, de asemenea, practica descrierii unor funcționalități ca o specificație (a se vedea procesul JCP), lăsând implementarea unor terți (eventual oferind o implementare de referință). Astfel, se rezolvă problema independenței față de producătorul de software.
În ciuda existenței Mono , C# leagă strâns dezvoltatorii de platforma Microsoft (inclusiv OS, soluții de birou). Astfel, utilizatorul de software scris în .NET de multe ori nu are de ales în utilizarea diferitelor componente ale sistemului. Acest lucru duce la așa-numita blocare a vânzătorului, în care producătorul de software terță parte poate dicta cumpărătorului aproape orice condiții pentru susținerea proiectului implementat. În timp ce utilizatorul unei aplicații Java, de regulă, poate alege furnizorul de software suplimentar (cum ar fi o bază de date, sistem de operare, server de aplicații etc.).
Java este mai vechi decât C# și construit pe o bază mare și activă de utilizatori, devenind lingua franca în multe domenii moderne ale informaticii, în special cele care implică rețele . Java domină cursurile de programare la universitățile și colegiile americane, iar literatura despre Java astăzi este mult mai mare decât cea despre C#. Maturitatea și popularitatea Java a dus la mai multe biblioteci și API-uri în Java (multe dintre ele sunt open source) decât în C#.
Spre deosebire de Java, C# este un limbaj relativ nou. Microsoft a studiat limbaje existente, cum ar fi Java, Delphi și Visual Basic și a schimbat unele aspecte ale limbajului pentru a se potrivi mai bine nevoilor anumitor tipuri de aplicații.
În ceea ce privește Java, se pot auzi critici că acest limbaj se dezvoltă lent, îi lipsesc unele caracteristici care facilitează modelele și metodologiile de programare la modă. Limbajul C# a fost criticat pentru că este poate prea rapid pentru a răspunde tendințelor actuale în programare cu prețul concentrării și simplității limbajului. Aparent, designerii Java au adoptat o atitudine mai conservatoare cu privire la adăugarea de noi caracteristici majore la sintaxa limbajului decât în alte limbi moderne – poate că nu dorește să lege limbajul de curente care ar putea duce la fundături pe termen lung. Odată cu lansarea Java 5.0, această tendință a fost în mare măsură inversată, deoarece a introdus câteva caracteristici majore ale limbajului: bucla de tip foreach, împachetare automată, metode variadice, tipuri enumerate, tipuri generice și adnotări (toate acestea sunt prezente și în C#). Începând cu Java 8, a început implementarea activă a noilor funcții, în special: expresii lambda, cuvântul cheie var, modularitatea în cadrul proiectului Jigsaw și așa mai departe.
C#, la rândul său, evoluează mai rapid, cu mult mai puțină reținere în adăugarea de noi caracteristici specifice domeniului. Această tendință a fost evidentă mai ales în versiunea C# 3.0, în care, de exemplu, au apărut interogări asemănătoare SQL . (Noile caracteristici sunt create pentru a rămâne un limbaj de uz general. Pentru mai multe despre C# 3.0, consultați articolul C# .) Au fost luate în considerare completările specifice domeniului la Java, dar, cel puțin până în prezent, au fost abandonate.
De la apariția C#, acesta a fost constant comparat cu Java. Este de netăgăduit că C# și CLR -ul său gestionat datorează foarte mult Java și JRE (Java Runtime Environment).
Este discutabil dacă dezvoltarea C# este în vreun fel rezultatul recunoașterii de către Microsoft că un mediu de cod gestionat condus de Java are multe avantaje într-o lume în rețea în creștere, în special odată cu apariția Internetului pe alte dispozitive decât computerele personale și cu creșterea importanța securității rețelei. Înainte de crearea C#, Microsoft a modificat Java (creând J++ ) pentru a adăuga caracteristici care rulează numai pe Windows , încălcând astfel acordul de licență Sun Microsystems . În timp ce Microsoft se afla în a doua fază a strategiei sale de afaceri, cunoscută sub numele de „ Embrace, Extend, and Extinguish ”, dezvoltarea J++ a fost oprită de un proces intentat de Sun. Interzis să dezvolte o clonă Java cu caracteristicile pe care le dorea, Microsoft a creat o alternativă care era mai în concordanță cu nevoile și viziunea lor pentru viitor.
În ciuda unui început atât de agitat, devine din ce în ce mai clar că cele două limbi rareori concurează una cu cealaltă pe piață. Java domină sectorul mobil și are o mulțime de urmăritori pe piața aplicațiilor web. C# a fost bine primit pe piața desktop Windows și datorită ASP.NET, C# este, de asemenea, un jucător pe piața aplicațiilor web.
Aplicații desktopPentru ambele limbi, există un set de biblioteci care oferă posibilitatea de a construi o interfață cu utilizatorul pentru aplicațiile desktop. În cazul Java, acestea sunt bibliotecile multiplatformă Swing și SWT , precum și platforma JavaFX, care vă permite să creați aplicații RIA. În principiu, oricare dintre ele vă permite să creați aplicații desktop multiplatforme în Java.
Pentru C# pe platforma Windows, principalele platforme pentru dezvoltarea aplicațiilor grafice desktop sunt platformele Windows Forms și WPF. Pentru dezvoltarea sub Windows 8 există o platformă specială WinRT . Pentru dezvoltarea Windows 10, există o platformă UWP dedicată. Pentru alte platforme se folosește biblioteca gtk#, realizată de proiectul Mono. Încercările de a implementa în mod liber Windows. Au fost făcute și se fac formulare (de exemplu, în proiectul DotGNU ), cu toate acestea, din cauza naturii închise a originalului, acestea suferă inevitabil de secundar și incomplet, cu greu pot concura cu implementarea de la Microsoft și, prin urmare, poate fi utilizat numai pentru portarea întârziată a aplicațiilor Windows pe alte platforme. Dezvoltarile care se bazează inițial pe Windows sunt de obicei construite pe Windows.Forms, iar portarea lor pe o altă platformă devine dificilă. Dezvoltarea Mono C# folosind gtk# este portabilă, dar mult mai puțin. Nu există nicio implementare a cadrului WPF în cadrul proiectului Mono, așa că aplicațiile WPF nu sunt portabile pe sistemele de operare bazate pe Linux.
C#, împreună cu Java, devine treptat popular pe mai multe sisteme de operare bazate pe Linux și BSD [26] [27] [28] . Implementarea proiectului Mono a fost un proces nedureros din punct de vedere legal, deoarece limbajele CLR și C# sunt standardizate de Ecma și ISO și oricine le poate implementa fără a-și face griji cu privire la latura legală a lucrurilor [29] . În același timp, trebuie menționat că o aplicație scrisă în mediul Windows poate avea probleme semnificative de lansare sub alt sistem de operare.
Aplicații mobileJ2ME (JavaME, Java(2) Micro Edition) are o bază foarte largă pe piețele de telefoane mobile și PDA , unde doar cele mai ieftine dispozitive nu au KVM (o mașină virtuală Java redusă pentru dispozitive cu resurse limitate). Programele Java, inclusiv multe jocuri, sunt omniprezente.
În timp ce aproape toate telefoanele includ KVM, aceste caracteristici nu sunt foarte folosite de majoritatea utilizatorilor. Aplicațiile Java de pe majoritatea telefoanelor constau de obicei în sisteme de meniu, jocuri mici etc. Aplicațiile cu drepturi depline pentru telefoane mobile sunt rare.
Java este folosit pentru a dezvolta aplicații pentru Android folosind mașina virtuală non-standard Dalvik (sau ART ).
C# este limbajul principal pentru scrierea aplicațiilor pentru sistemul de operare mobil Windows Phone dezvoltat de Microsoft. Cu toate acestea, există un cadru de dezvoltare multiplatform Xamarin care vă permite să creați aplicații native pentru Android, IOS și Windows Phone.
Java | |
---|---|
Platforme | |
Sun Technologies | |
Tehnologii cheie ale terților | |
Poveste |
|
Proprietățile limbajului | |
Limbaje de scripting |
|
conferințe Java |
|