Développons en Java 2.30 | |
Copyright (C) 1999-2022 Jean-Michel DOUDOUX | (date de publication : 15/06/2022) |
|
Niveau : | Supérieur |
En l'an 2000, Microsoft dévoile sa nouvelle plate-forme de développement nommée .Net et un nouveau langage de développement dédié : C#. C# est un des langages utilisables pour développer des applications de tous types (standalone, web, mobile, ...) pour la plate-forme .Net de Microsoft.
Java et C# partagent un ensemble de fonctionnalités communes :
Java et C# possèdent cependant des différences :
C# et Java possèdent chacun des points forts dont certains sont communs. Ces deux langages se distinguent cependant sur de nombreux points particuliers. Le but de ce chapitre n'est pas de les comparer pour déterminer quel serait le « meilleur » mais simplement de recenser une partie de leurs points communs et de leurs différences ceci afin de faciliter le passage de l'un à l'autre et vice versa.
Le contenu de ce chapitre n'est pas exhaustif et propose simplement d'aborder les points principaux. Il concerne essentiellement Java 6 et C# 2.0.
Chaque langage possède des fonctionnalités importantes qui lui sont propres notamment :
Il est intéressant de remarquer que certaines fonctionnalités d'un des langages sont incorporées dans l'autre et vice versa au fur et à mesure de leurs nouvelles versions (exemple : les fonctionnalités boxing/unboxing, enumération, parcours de collections et les generics ajoutés dans Java 5.0, les classes anonymes ajoutées dans C# 2.0 ou les classes anonymes internes ajoutées dans C# 3.0).
Ce chapitre contient plusieurs sections :
La syntaxe de Java et C# sont relativement proches puisqu'elles dérivent pour les deux de celle du langage C :
Il y a énormément de similitudes entre les mots clés des deux langages, presque tous les mots-clés Java ont un équivalent en C# à part quelques exceptions telles que transient, throws ou strictfp. C# possède de nombreux mots clés sans équivalent en Java. Le tableau ci-dessous recense les mots clés des deux langages avec leur équivalence en Java (même si leur rôle n'est pas toujours exactement le même).
C# |
Java |
C# |
Java |
C# |
Java |
C# |
Java |
abstract |
abstract |
false |
false |
override |
typeof |
||
as |
finally |
finally |
params |
uint |
|||
base |
super |
fixed |
partial (C# 2) |
ulong |
|||
bool |
boolean |
float |
float |
private |
private |
unchecked |
|
break |
break |
for |
for |
protected |
unsafe |
||
byte |
foreach |
for (java 5) |
public |
public |
ushort |
||
case |
case |
get |
readonly |
using |
import |
||
catch |
catch |
goto |
ref |
value |
|||
char |
char |
if |
if |
return |
return |
virtual |
|
checked |
implicit |
sbyte |
byte |
volatile |
volatile |
||
class |
class |
in |
sealed |
final |
void |
void |
|
const |
int |
int |
set |
where (C# 2) |
|||
continue |
continue |
interface |
interface |
short |
short |
while |
while |
decimal |
internal |
protected |
sizeof |
yield (C# 3) |
|||
default |
default |
is |
instanceof |
stackalloc |
: |
extends |
|
delegate |
lock |
synchronized |
static |
static |
: |
implements |
|
do |
do |
long |
long |
string |
assert (java 4) |
||
double |
double |
namespace |
package |
struct |
strictfp |
||
else |
else |
new |
new |
switch |
switch |
throws |
|
enum |
enum (java 5) |
null |
null |
this |
this |
transient |
|
event |
object |
throw |
throw |
||||
explicit |
operator |
true |
true |
||||
extern |
native |
out |
try |
try |
Remarque : les équivalences entre les mots clés Java et C# du tableau ci-dessus ne sont pas toujours parfaites mais les deux mots présentent des similitudes plus ou moins importantes dans leur principal rôle.
Java définit const et goto dans ses mots clés mais ne les utilise pas dans sa syntaxe pour le moment.
C# propose des raccourcis syntaxiques, par exemple :
Mot clé |
Remplacé à la compilation par |
String ou string |
System.String |
Object ou object |
System.Object |
Le mot clé string de C# est donc un alias sur le type correspondant String de la plate-forme .NET. Java utilise la classe java.lang.String mais Java ne définit aucun mot clé pour cette classe.
Pour organiser les classes, Java utilise le concept de packages et C# utilise le concept de Namespaces. La grande différence est qu'avec Java le nom du packages impose une structure de répertoires correspondante. Avec C#, les namespaces sont purement indicatifs.
En Java, un fichier ne peut donc appartenir qu'à un seul package alors qu'en C#, un fichier peut déclarer plusieurs namespaces.
L'utilisation de cette liberté dans la mise en oeuvre des namespaces n'est cependant pas recommandée et il est préférable de s'imposer quelques règles simples pour se faciliter la tâche notamment dans de gros projets.
Le contenu d'un fichier source est soumis à quelques contraintes en Java : le nom du fichier doit correspondre à la casse près au nom de l'unique classe publique qu'il contient. Il est possible de définir d'autres classes dans le fichier mais elles ne peuvent pas être public. En C#, il n'y a aucune contrainte : le nom du fichier est libre et peut contenir plusieurs classes publiques.
Là encore, l'utilisation de cette fonctionnalité n'est pas recommandée. Il est préférable de s'imposer quelques règles simples pour se faciliter la tâche notamment dans de gros projets.
En C#, les éléments du code source (classes, structs, delegates, enums, ...) sont organisés en fichiers, namespaces et assemblies.
Un namespace permet de regrouper des éléments du code de façon similaire au package en Java. Cependant, un package définit une structure physique de répertoires qui correspond au nom du package. Un namespace définit uniquement une structure logique.
L'utilisation des éléments d'un namespace se fait avec l'instruction using.
Exemple C# : |
namespace fr.jmdoudoux.dej
{
public class MaClasse
{
public void MaClasse()
{
}
}
} |
Exemple Java : |
package fr.jmdoudoux.dej;
public class MaClasse {
public void maMethode() {
}
} |
En C#, il est possible d'imbriquer plusieurs namespaces.
Exemple C# : |
namespace fr.jmdoudoux.dej
{
public class MaClasse
{
public void MaMethode()
{
}
}
namespace fr.jmdoudoux.dej.donnees
{
public class MaDonnee
{
public void Afficher()
{
}
}
}
} |
Une assembly est l'unité de packaging fondamentale de .Net : elle regroupe des classes compilées en MSIL, des métadonnées et des ressources. Une assembly peut contenir plusieurs namespaces et un namespace peut être réparti sur plusieurs assemblies. Un numéro de version est géré au niveau de l'assembly.
Un fichier JAR est au format zip alors qu'une assembly peut être au format exe ou dll.
Java propose aussi des packagings dédiés pour certains types d'applications : ear (pour les applications d'entreprises), war pour les applications web, ...
C# et Java sont sensibles à la casse. Ils proposent tous les deux leurs propres conventions de nommage pour les entités mises en oeuvre dans le code source. Leur utilisation n'est pas obligatoire mais elles sont communément appliquées.
|
La suite de cette section sera développée dans une version future de ce document
|
Java et C# sont tous les deux des langages orientés objets : ils proposent donc des types primitifs et des types objets pour les données. C# propose en plus le type valeur.
Chaque type primitif Java possède un type équivalent de même nom en C# sauf byte qui est signé en Java et correspond donc au type sbyte en C#.
C# possède en plus des types numériques non signés (byte, ushort, uint, ulong).
C# propose aussi le type decimal qui permet de stocker un nombre décimal avec une moins grande capacité mais une meilleure précision et sans erreur d'arrondi.
Les types primitifs de .Net sont définis dans le CTS (Common Type System)
C# |
Java |
||||
Type |
taille |
valeurs |
type |
taille |
valeurs |
Byte |
8 |
0 à 255 |
|||
Sbyte |
8 |
-128 à 127 |
byte |
8 |
|
Short |
16 |
-32768 à 32767 |
short |
16 |
|
Ushort |
16 |
0 à 65635 |
|||
Int |
32 |
int |
32 |
-2147483648 à 2147483647 |
|
Uint |
32 |
||||
Long |
64 |
long |
-9223372036854775808 à 9223372036854775807 |
||
Ulong |
64 |
||||
Float |
32 |
float |
32 |
1.401e-045 à 3.40282e+038 |
|
Double |
64 |
double |
64 |
2.22507e-308 à 1.79769e+308 |
|
Decimal |
96 |
C# utilise le suffixe f pour les valeurs de type float, d pour les valeurs de type double (suffixe par défaut) et m pour les valeurs de type décimal.
Java et C# proposent tous les deux la définition et la manipulation d'objets. Leur mise en oeuvre est détaillée dans une section dédiée.
C# permet au travers des structures (structs) de stocker des objets dans la pile plutôt que sur le tas. Ces objets sont nommés type valeur (ValueType) par opposition au type référence. Les types valeurs sont donc stockés dans la pile : ils sont toujours passés par valeur et ne sont pas gérés par le ramasse-miettes.
L'utilisation de la pile par rapport au tas possède plusieurs avantages : la gestion mémoire est plus rapide et sans fragmentation.
En C#, tous les types primitifs sont encapsulés dans des structures qui héritent de ValueType.
Tous les objets de type struct héritent implicitement de la classe object et ne peuvent pas hériter d'une autre classe mais peuvent implémenter des interfaces. L'héritage n'est pas supporté pour les types struct. Ainsi les types struct sont implicitement marqués avec le modificateur sealed et ne peuvent donc pas être abstraits.
Exemple C# : |
using System;
namespace TestCS
{
struct Position
{
private int coordX;
public int CoordX
{
get { return coordX; }
set { coordX = value; }
}
private int coordY;
public int CoordY
{
get { return coordY; }
set { coordY = value; }
}
public Position(int x, int y)
{
this.coordX = x;
this.coordY = y;
}
public override string ToString()
{
return String.Format("(Position = {0}, {1})", coordX, coordY);
}
}
} |
Java définit une constante grâce au mot clé final : elle ne peut être initialisée qu'une seule fois de façon statique (à la compilation) ou dynamique (à l'exécution uniquement dans un constructeur). Une fois la valeur affectée, elle ne peut plus être modifiée.
Exemple Java : |
public class TestFinal {
public final int valeurA;
public final int valeurB = 20;
public TestFinal() {
valeurA = 10;
}
public static void main(String[] args) {
TestFinal f = new TestFinal();
System.out.println("valeurA="+f.valeurA);
System.out.println("valeurB="+f.valeurB);
}
} |
C# utilise le mot clé const pour définir une constante statique (valorisation à la compilation) et le mot clé readonly pour une constante dynamique (à l'exécution uniquement dans un constructeur ou à l'initialisation de la variable).
Exemple Java : |
using System;
namespace TestCS
{
class TestConstantes
{
const int i = 10;
readonly int j;
public TestConstantes(int valeur)
{
j = valeur;
Console.Out.WriteLine("i="+i);
Console.Out.WriteLine("j="+j);
}
}
} |
Java et C# proposent un jeu d'instructions similaire. Quelques différences existent notamment avec l'instruction switch et goto.
La syntaxe de l'instruction switch est similaire en Java et C#. Cependant C# possède deux différences :
Exemple C# : |
public static string formatterValeur(string code)
{
string resultat = "";
switch(code)
{
case "A":
case "B":
resultat = "Bien";
break;
case "C":
resultat = "Mauvais";
break;
default :
resultat = "Inconnu";
break;
}
return resultat;
} |
Goto est un mot clé Java, mais il ne correspond pas à une instruction du langage : c'est uniquement un mot réservé pour le moment.
C# propose l'instruction goto qui permet de débrancher l'exécution du code vers une étiquette (label) définie.
L'utilisation de cette instruction est cependant fortement déconseillée depuis de nombreuses années.
Exemple C# : |
private void button1_Click(object sender, EventArgs e)
{
int compteur = 0;
incrementation:
compteur++;
listBox1.Items.Add("element "+compteur);
if (compteur < 5)
{
goto incrementation;
}
} |
C# propose l'instruction foreach pour faciliter le parcours dans son intégralité d'une collection qui implémente l'interface System.Collections.IEnumerable.
Exemple C# : |
string[] donnees = { "element1", "element2", "element3", "element4" };
foreach (string element in donnees)
Console.WriteLine(element); |
A partir de Java 1.5 la même fonctionnalité est proposée. Auparavant, pour parcourir un tableau java, il fallait utiliser une boucle pour itérer sur chaque élément du tableau.
Exemple Java : |
package fr.jmdoudoux.dej;
public class TestParcoursTableau {
public static void main(String[] args) {
String[] donnees = { "element1", "element2", "element3", "element4" };
for (int i = 0; i < donnees.length; i++) {
System.out.println(donnees[i]);
}
}
} |
Pour les collections, il fallait utiliser un objet de type Iterator.
Exemple Java: |
package fr.jmdoudoux.dej;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TestParcoursListe {
public static void main(String[] args) {
List donnees = new ArrayList();
donnees.add("element1");
donnees.add("element2");
donnees.add("element3");
donnees.add("element4");
for (Iterator iter = donnees.iterator(); iter.hasNext();) {
String element = (String) iter.next();
System.out.println(element);
}
}
} |
Java 1.5 propose une version différente de l'instruction for permettant de réaliser un parcours sur un ensemble de données.
Exemple Java : |
package fr.jmdoudoux.dej;
public class TestParcoursTableauFor {
public static void main(String[] args) {
String[] donnees = { "element1", "element2", "element3", "element4" };
for (String element : donnees) {
System.out.println(element);
}
}
} |
Dans les collections, il faut utiliser les generics pour typer leurs éléments.
Exemple Java : |
package fr.jmdoudoux.dej;
import java.util.ArrayList;
import java.util.List;
public class TestParcoursListeFor {
public static void main(String[] args) {
List<String> donnees = new ArrayList<String>();
donnees.add("element1");
donnees.add("element2");
donnees.add("element3");
donnees.add("element4");
for(String element : donnees) {
System.out.println(element);
}
}
} |
C# propose un support des metadatas dans le langage avec les attributs.
Avant Java 5.0 les seules metadatas utilisables en Java étaient celles de l'outil Javadoc insérées dans des commentaires dédiés. Le seul tag utilisable par le compilateur est le tag @deprecated qui signale que la méthode ne devrait plus être utilisée et permet au compilateur d'afficher un avertissement. Tous les autres tags ne sont utilisés que pour la génération de la documentation.
Depuis la version 5.0, Java intègre les annotations dans le langage.
|
La suite de cette section sera développée dans une version future de ce document
|
C# propose la définition d'énumérations en utilisant le mot clé enum. Par défaut, chaque membre de l'énumération a pour valeur un entier incrémenté pour chaque valeur, la première étant 0.
Exemple C# : |
public enum StatutOperation {
ouverte,
traitee,
enAttente,
fermee
} |
Il est possible de forcer le type de l'énumération et la valeur d'un de ses membres.
Exemple C# : |
public enum StatutOperation : byte
{
ouverte = 10,
traitee = 20 ,
enAttente = 30,
fermee = 40
} |
Exemple C# : |
static void Main(string[] args)
{
StatutOperation statut = StatutOperation.ouverte |
StatutOperation.enAttente;
if ((statut & StatutOperation.enAttente) != 0)
{
Console.WriteLine("En attente");
}
Console.ReadLine();
} |
Java propose depuis la version 1.5 un support des énumérations en utilisant le mot clé enum. Les énumérations en Java sont converties par le compilateur en une classe.
Exemple Java : |
public enum StatutOperation {
ouverte,
traitee,
enAttente,
fermee
} |
Exemple Java : |
public class Operation {
private StatutOperation operation;
private String libelle;
public String getLibelle() {
return libelle;
}
public StatutOperation getOperation() {
return operation;
}
public Operation(String libelle) {
super();
this.libelle = libelle;
this.operation = StatutOperation.ouverte;
}
public void Fermer()
{
this.operation = StatutOperation.fermee;
}
} |
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
C# permet de gérer ou ignorer le débordement de capacité lors d'un downcast (conversion vers un type plus petit). Les mots clés checked et unchecked permettent respectivement d'activer ou de désactiver ce contrôle dans le bloc de code qu'ils définissent.
Exemple C# : |
class MaClasseChecked
{
public MaClasseChecked()
{
int valeur = 1000;
try
{
checked
{
byte b1 = (byte)valeur;
}
}
catch (OverflowException ofe)
{
Console.Out.WriteLine(ofe.StackTrace);
}
try
{
unchecked
{
byte b2 = (byte)valeur;
}
}
catch (OverflowException ofe)
{
Console.Out.WriteLine(ofe.StackTrace);
}
}
} |
|
La suite de cette section sera développée dans une version future de ce document
|
Les points d'entrée d'une application Java et C# sont la méthode main() en Java et Main() en C# d'une classe de cette application.
Exemple C# : |
using System;
class MaClasse{
public static void Main(String[] args){
Console.WriteLine("Hello World");
}
} |
Exemple Java : |
class MaClasse{
public static void main(String[] args){
System.out.println("Hello World");
}
} |
Java propose d'avoir une méthode main() dans plusieurs classes de l'application. Lors de l'exécution de l'application, il suffit de fournir en paramètre de la JVM le nom de la classe à exécuter. Java permet donc d'avoir plusieurs points d'entrée dans une application.
La version compilée d'une application en C# ne peut avoir qu'un seul point d'entrée. Si plusieurs classes possèdent une méthode main, il est nécessaire de préciser la classe servant de point d'entrée grâce à l'option /main du compilateur.
Résultat : |
C:\>csc /main:MaClass1 /out:MonApp.exe MaClasse1.cs MaClasse2.cs |
Sous Visual Studio, dans l'onglet Application, il faut utiliser l'option "open" du menu contextuel sur les properties du projet et sélectionner la classe qui fait office de point d'entrée dans la liste déroulante « Startup Objet ».
La classe mère de tous les objets est java.lang.Object en Java et System.Object en .Net : elles possèdent des méthodes ayant des rôles similaires (toString()/ToString(), ...). La classe java.lang.Object propose en plus des méthodes utilisées lors de la synchronisation de threads (notify(), notifyAll() et wait()).
C# propose un alias pour la classe Object : le mot clé object est remplacé à la compilation par System.Object.
En Java et en C#, toutes les méthodes doivent être membre d'une classe.
Java et C# ne proposent pas l'héritage multiple au niveau des classes mais le permettent au niveau des interfaces.
Java et C# supportent l'héritage avec une syntaxe différente : Java utilise le mot clé extends pour l'héritage de classe et le mot clé implements pour l'implémentation d'interface. C# utilise la syntaxe du C++ pour définir l'héritage ou l'implémentation d'une interface.
Exemple Java : |
public class MaClasseFille extends MaClasseMere implements Comparable {
...
} |
Exemple C# : |
public class MaClasseFille : MaClasseMere, IComparable
{
...
} |
Pour empêcher le sous-classement d'une classe, il faut utiliser le mot clé final en Java et sealed en C#.
Java et C# permettent la définition de constructeurs static (permettant d'initialiser les variables statiques au chargement de la classe)
Exemple Java : |
static {
System.out.println(« initialisation »);
} |
Exemple C# : |
static MaClasse()
{
Console.WriteLine(« initialisation »);
} |
L'opérateur instanceof en Java et is en C# permettent de déterminer si une instance est du type précisé.
Java et C# proposent la définition d'interfaces.
C# propose en plus l'implémentation explicite d'une interface. Ceci permet d'éviter les conflits de nom dans l'implémentation des méthodes de chaque interface.
Exemple C# : |
public interface IMonIterface1
{
void MaMethode();
}
public interface IMonInterface2
{
void MaMethode();
}
public class MonImplementation : IMonIterface1, IMonInterface2
{
void IMonIterface1.MaMethode()
{
}
void IMonInterface2.MaMethode()
{
}
} |
Dans ce cas, pour appeler l'une ou l'autre des méthodes, il faut obligatoirement downcaster l'objet vers l'interface de la méthode concernée.
Exemple C# : |
MonImplementation mi = new MonImplementation();
((IMonIterface1)mi).MaMethode(); |
En Java, il est possible de définir des constantes dans une interface : ces constantes seront ajoutées dans les classes qui implémentent l'interface.
Exemple Java : |
public interface MonInterface {
public final static int maValeur = 100;
} |
Pour assurer la mise en oeuvre de l'encapsulation, Java et C# propose un ensemble de modificateurs d'accès :
C# |
Java |
|
private |
private |
Visible uniquement dans le type |
public |
public |
Visible par tout le mode |
aucun : "package-private" |
Visible uniquement pour les classes du même package |
|
internal |
Visible uniquement depuis une classe de la même assembly |
|
protected |
Visible uniquement dans la classe ou ses classes filles indépendamment de l'assembly |
|
internal protected |
protected |
Visible uniquement dans la classe ou ses classes filles ou depuis une classe de la même assembly en .Net ou du même package en Java |
Par défaut une méthode est package-private en Java et private en C#.
En C#, internal et internal protected ne sont pas utilisables sur les membres des structures.
En Java, il est possible d'initialiser une variable d'instance avec la valeur d'une autre variable d'instance.
Exemple Java : |
package fr.jmdoudoux.dej;
public class MaClasse {
int x = 0;
int y = x + 10;
} |
En C#, il n'est pas possible d'initialiser une variable d'instance avec la valeur d'une autre variable d'instance : une erreur CS0236 (A field initializer cannot reference the nonstatic field, method, or property 'field')
Exemple C# : |
namespace fr.jmdoudoux.dej
{
public class MaClasse
{
int x = 0;
int y = x + 10;
}
} |
La seule solution est de réaliser cette initialisation dans le constructeur
Exemple C# : |
namespace fr.jmdoudoux.dej
{
public class MaClasse
{
int x = 0;
int y = 0;
public MaClasse()
{
y = x + 10;
}
}
} |
Les propriétés permettent la mise en oeuvre de l'encapsulation en offrant un contrôle sur l'accès des champs d'un objet.
Java ne propose aucune fonctionnalité spécifique dans sa syntaxe mais recommande au travers des spécifications des Javabeans de définir des méthodes de type getter et setter nommées plus généralement accesseurs.
Exemple Java : |
private int taille;
public int getTaille() {
return taille;
}
public void setTaille (int value) {
this.taille = value;
} |
Les propriétés en C# sont similaires à celles proposées par Delphi.
Exemple C# : |
private int _taille;
public int Taille
{
get { return _taille; }
set { _taille = value; }
} |
Le mot clé value fait référence à la valeur fournie.
Syntaxiquement, l'utilisation d'une propriété se fait avec la même syntaxe que pour un champ mais en réalité ce sont les méthodes get et set qui sont invoquées de manière transparente.
Il est possible de définir des propriétés en lecture seule, en écriture seule ou en lecture/écriture selon que les méthodes get et set soient définies ou non.
La déclaration est donc plus concise grâce aux propriétés et leur utilisation l'est aussi.
Exemple Java : |
setTaille(getTaille()+1) ; |
Exemple C# : |
Taille++; |
Cependant, la syntaxe de l'utilisation d'une propriété ne permet pas de savoir si c'est un champ ou une propriété qui est utilisée mais cela facilite le remplacement d'un champ par une propriété.
Il est possible de lever une exception dans le code de mise à jour de la valeur d'une propriété. Ceci peut être cependant déroutant d'avoir la levée d'une exception lors de l'affectation d'une valeur.
|
La suite de cette section sera développée dans une version future de ce document
|
C# et Java permettent la surcharge des constructeurs et l'appel dans un constructeur d'une autre surcharge du constructeur. C# utilise le mot clé this précédé du caractère : et des éventuels paramètres entre parenthèses dans la signature du constructeur
Java utilise le mot clé this suivi des éventuels paramètres entre parenthèses dans le corps du constructeur.
Java et C# appellent automatiquement le constructeur hérité : ceci garantit qu'une instance de la classe fille ne soit pas dans un état inconsistant.
Exemple Java : |
public class ClasseA {
public ClasseA() {
System.out.println("invocation constructeur ClasseA");
}
public void afficher()
{
System.out.println("ClasseA");
}
} |
Exemple Java : |
public class ClasseB extends ClasseA {
public ClasseB() {
System.out.println("invocation constructeur ClasseB");
}
public void afficher()
{
System.out.println("ClasseB");
}
} |
Exemple Java : |
public class Test {
public static void main(String[] args) {
ClasseA classeA = new ClasseA();
classeA.afficher();
ClasseB classeB = new ClasseB();
classeB.afficher();
}
} |
Résultat : |
Résultat :
invocation constructeur ClasseA
ClasseA
invocation constructeur ClasseA
invocation constructeur ClasseB
ClasseB |
Exemple C# : |
class ClasseA
{
public ClasseA()
{
Console.WriteLine("Invocation constructeur ClasseA");
}
public virtual void Afficher()
{
Console.WriteLine("ClasseA");
}
} |
Exemple C# : |
class ClasseB : ClasseA
{
public ClasseB()
{
Console.WriteLine("Invocation constructeur ClasseB");
}
public override sealed void Afficher()
{
Console.WriteLine("ClasseB");
}
} |
Exemple C# : |
class Program
{
public static void Main(String[] args)
{
ClasseA classeA = new ClasseA();
classeA.Afficher();
ClasseB classeB = new ClasseB();
classeB.Afficher();
}
} |
Résultat : |
Invocation constructeur ClasseA
ClasseA
Invocation constructeur ClasseA
Invocation constructeur ClasseB
ClasseB |
C# et Java permettent aussi un appel explicite à un constructeur père. Cet appel est obligatoire dans les deux langages sous peine d'avoir une erreur de compilation lorsqu'un constructeur est défini dans une classe fille sans que la classe mère ne possède un constructeur avec les mêmes paramètres.
C# utilise le mot clé base précédé du caractère ":" et des éventuels paramètres entre parenthèses dans la signature du constructeur.
Java utilise le mot clé super suivi des éventuels paramètres entre parenthèses dans le corps du constructeur.
Exemple C# : |
using System;
using System.Collections.Generic;
using System.Text;
namespace TestCS
{
class MaClasseMere
{
private int id;
public MaClasseMere(int id)
{
this.id = id;
Console.Out.WriteLine("MaClasseMere id="+id);
}
}
class MaClasse : MaClasseMere
{
private string libelle;
public MaClasse(int id, string libelle)
: base(id)
{
this.libelle = "";
Console.Out.WriteLine("MaCLasse id="+id);
Console.Out.WriteLine("MaClasse libelle="+libelle);
}
public MaClasse(int id)
: this(id,"")
{
}
}
} |
Exemple Java : |
public class MaClasseMere {
private int id;
public MaClasseMere(int id) {
this.id = id;
System.out.println("MaClasseMere id=" + id);
}
}
public class MaClasse extends MaClasseMere {
private String libelle;
public MaClasse(int id, String libelle) {
super(id);
this.libelle = "";
System.out.println("MaCLasse id=" + id);
System.out.println("MaClasse libelle=" + libelle);
}
public MaClasse(int id) {
this(id, "");
}
} |
En Java et en C#, il est possible d'invoquer un autre constructeur dans un constructeur : ceci permet de réduire la duplication de code dans les différents constructeurs.
En Java et en C#, les constructeurs ne sont pas hérités : chaque classe ne possède que les constructeurs qu'elle définit.
Exemple C# : |
class ClasseA
{
public ClasseA()
{
Console.WriteLine("Invocation constructeur ClasseA");
}
public virtual void Afficher()
{
Console.WriteLine("ClasseA");
}
}
class ClasseB : ClasseA
{
public ClasseB(int valeur)
{
Console.WriteLine("Invocation constructeur ClasseB");
}
public void Afficher()
{
Console.WriteLine("ClasseB");
}
} |
Exemple C# : |
'ApplicationTest.ClasseB' does not contain a constructor that takes '0' arguments
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
En Java, les arguments de type primitifs sont toujours passés en paramètres par valeur (les arguments sont copiés dans la pile). Pour pouvoir modifier la valeur d'un type primitif fourni en paramètre d'une méthode, il faut l'encapsuler dans son wrapper et ainsi le passer sous forme d'objet en paramètre.
En C#, il est possible de préciser si le passage se fait par valeur ou par référence selon l'utilisation des mots clés ref et out.
|
La suite de cette section sera développée dans une version future de ce document
|
En C#, le mot clé params permet de préciser qu'un paramètre pourra accepter plusieurs occurrences. La syntaxe dans la signature de la méthode est le mot clé params suivi d'un tableau du type de données puis du nom du paramètre.
En Java, cette fonctionnalité est disponible à partir de la version 5 en utilisant la notation ... précédée du type et du nom de la variable dans la signature de la méthode. Le compilateur va transformer ce paramètre en un tableau du type précisé et c'est ce tableau qui sera manipulé dans le corps de la méthode.
En Java et en C# :
Exemple C# : |
class TestParams
{
public static void Main(String[] args)
{
Console.WriteLine("valeur a="+ajouter(1, 2, 3, 4));
Console.WriteLine("valeur b="+ajouter(new int[] { 1, 2, 3, 4 }));
}
public static long ajouter(params int[] array)
{
long retour = 0;
foreach (int i in array)
{
retour += i;
}
return retour;
}
} |
Exemple Java : |
class TestParams {
public static void main(String Args[]) {
System.out.println("valeur a="+ajouter(1,2,3,4));
System.out.println("valeur b="+ajouter(new int[] { 1, 2, 3, 4 }));
}
public static long ajouter(int ... valeurs) {
long retour = 0l;
for (int i : valeurs ) {
retour += i;
}
return retour;
}
} |
Java et C# ne proposent pas de support pour l'héritage multiple mais ils proposent tous les deux les interfaces.
Java propose une syntaxe différente pour l'héritage (extends) et l'implémentation (implements).
Exemple Java : |
package fr.jmdoudoux.dej.heritage;
public class MaClasse extends MaClasseMere implements MonInterfaceA, MonInterfaceB {
}
class MaClasseMere{}
interface MonInterfaceA {}
interface MonInterfaceB {} |
En C#, il n'y a pas de distinction syntaxique entre un héritage et l'implémentation d'une interface : ils se font tous les deux avec le nom de la classe suivi du caractère deux-points puis, éventuellement, de la classe mère et/ou d'une ou plusieurs interfaces séparées par un caractère virgule.
Exemple C# : |
namespace fr.jmdoudoux.dej.heritage
{
public class MaClasse : MaClasseMere, IMonInterfaceA, IMonInterfaceB
{
}
class MaClasseMere { }
interface IMonInterfaceA { }
interface IMonInterfaceB { }
} |
Remarque : par convention, les interfaces commencent par un I majuscule en C#, ce qui permet facilement de les identifier dans la définition d'une classe sous réserve que cette convention soit appliquée.
En Java et en C#, les constructeurs ne sont pas hérités : seuls les constructeurs définis dans la classe sont utilisables.
En Java, pour appeler un constructeur de la classe mère, il faut utiliser le mot clé super() dans le corps du constructeur en lui passant les éventuels paramètres.
Exemple Java : |
package fr.jmdoudoux.dej;
public class MonException extends Exception {
public MonException() {
super();
}
public MonException(String message, Throwable cause) {
super(message, cause);
}
public MonException(String message) {
super(message);
}
public MonException(Throwable cause) {
super(cause);
}
} |
C# propose le mot clé base à utiliser dans la signature du constructeur avec les éventuels paramètres.
Exemple C# : |
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace fr.jmdoudoux.dej
{
[Serializable]
public class MonException : ApplicationException
{
public MonException() : base() { }
public MonException(string message) : base(message) { }
public MonException(string message, Exception inner) : base(message, inner) { }
protected MonException(SerializationInfo info, StreamingContext ctx) : base(info, ctx)
{ }
}
} |
En Java, pour empêcher une classe d'être dérivée (ou sous-classée), il faut utiliser le modificateur final dans la déclaration de la classe. C# utiliser le mot clé sealed.
Exemple Java : |
public final class MaClasseMere { } |
Exemple C# : |
public sealed class MaClasseMere { }
|
En Java, toutes les méthodes sont virtuelles. Il suffit simplement de redéfinir la méthode dans une classe fille pour mettre en oeuvre le polymorphisme.
Exemple Java : |
public class ClasseA {
public void afficher() {
System.out.println("ClasseA");
}
}
public class ClasseB extends ClasseA {
public void afficher() {
System.out.println("ClasseB");
}
}
public class TestPolymorph {
public static void main(String[] args) {
ClasseA classeA = new ClasseA();
classeA.afficher();
ClasseB classeB = new ClasseB();
classeB.afficher();
ClasseA classe = new ClasseB();
classe.afficher();
}
} |
Résultat : |
ClasseA
ClasseB
ClasseB |
Ceci facilite la vie des développeurs puisque ce mécanisme est mis en oeuvre implicitement mais cela peut poser des soucis de performance puisqu'à l'exécution d'une méthode, il faut parcourir la hiérarchie des classes filles pour trouver une éventuelle redéfinition à utiliser. En plus de nuire aux performances, cela compromet les optimisations réalisables par la machine virtuelle.
Lorsqu'une méthode ne sera pas redéfinie dans une classe fille, les développeurs Java peuvent la déclarer avec le modificateur final pour éviter cette recherche.
Le fait que toutes les méthodes soient virtuelles peut aussi être source d'erreurs difficiles à détecter.
Exemple Java : |
public class ClasseA {
public void afficher() {
System.out.println("ClasseA");
}
}
public class ClasseB extends ClasseA {
public void aficher() {
System.out.println("ClasseB");
}
}
public class TestPolymorph {
public static void main(String[] args) {
ClasseA classeA = new ClasseA();
classeA.afficher();
ClasseB classeB = new ClasseB();
classeB.afficher();
ClasseA classe = new ClasseB();
classe.afficher();
}
} |
Résultat : |
ClasseA
ClasseA
ClasseA |
Pour pallier cette difficulté, Java 5 a introduit l'annotation standard @Override qui permet au compilateur de vérifier que la classe redéfinit bien une méthode définie dans la classe mère.
Exemple Java : |
public class ClasseB extends ClasseA {
@Override
public void aficher() {
System.out.println("ClasseB");
}
} |
Résultat : |
C:\java\Test\src\com\jmd\test>javac ClasseB.java
ClasseB.java:3: cannot find symbol
symbol: class ClasseA
public class ClasseB extends ClasseA {
^
ClasseB.java:5: method does not override or implement a method from a supertype
@Override
^
2 errors |
Enfin, le fait que toutes les méthodes soient virtuelles peut permettre dans une classe fille de redéfinir involontairement une méthode.
En C#, aucune méthode n'est virtuelle. Chaque méthode qui pourra être redéfinie doit être déclarée avec le mot clé virtual. Chaque méthode redéfinie doit être déclarée avec le mot clé override. Si ce n'est pas le cas, aucune erreur n'est signalée par le compilateur et le comportement ne sera pas celui attendu.
Pour déclarer une méthode virtuelle, il faut utiliser le mot clé virtual dans la déclaration de la méthode. Cependant cela ne suffit pas, car dans ce cas, aucune méthode redéfinie n'est trouvée dans la hiérarchie d'objets.
Exemple C# : |
class ClasseA
{
public virtual void Afficher()
{
Console.WriteLine("ClasseA");
}
}
class ClasseB : ClasseA
{
public void Afficher()
{
Console.WriteLine("ClasseB");
}
}
class Program
{
public static void Main(String[] args)
{
ClasseA classeA = new ClasseA();
classeA.Afficher();
ClasseB classeB = new ClasseB();
classeB.Afficher();
ClasseA classe = new ClasseB();
classe.Afficher();
}
} |
Résultat : |
ClasseA
ClasseB
ClasseA |
Voici l'exemple ou le polymorphisme est correctement mis en oeuvre en déclarant la méthode de la classe virtuelle avec le mot clé virtual et en redéfinissant la méthode de la classe fille avec le mot clé override.
Exemple C# : |
class ClasseA
{
public virtual void Afficher()
{
Console.WriteLine("ClasseA");
}
}
class ClasseB : ClasseA
{
public override void Afficher()
{
Console.WriteLine("ClasseB");
}
}
class Program
{
public static void Main(String[] args)
{
ClasseA classeA = new ClasseA();
classeA.Afficher();
ClasseB classeB = new ClasseB();
classeB.Afficher();
ClasseA classe = new ClasseB();
classe.Afficher();
}
} |
Résultat : |
ClasseA
ClasseB
ClasseB |
Si le mot clé override est utilisé sur une méthode redéfinie de la classe mère qui ne possède pas le mot clé virtual alors il y a une erreur de compilation.
Exemple C# : |
class ClasseA
{
public void Afficher()
{
Console.WriteLine("ClasseA");
}
}
class ClasseB : ClasseA
{
public override void Afficher()
{
Console.WriteLine("ClasseB");
}
} |
Résultat : |
'ApplicationTest.ClasseB.Afficher()': cannot override inherited member
'ApplicationTest.ClasseA.Afficher()' because it is not marked virtual, abstract, or override
C:\Documents and Settings\jmd\My Documents\Visual Studio 2008\
Projects\ApplicationTest\ApplicationTest\ClasseB.cs
10 30 ApplicationTest |
Il est aussi possible d'utiliser le mot clé new dans la déclaration de la méthode redéfinie pour préciser explicitement que la méthode masque la méthode héritée.
Exemple C# : |
class ClasseA
{
public virtual void Afficher()
{
Console.WriteLine("ClasseA");
}
}
class ClasseB : ClasseA
{
public new void Afficher()
{
Console.WriteLine("ClasseB");
}
} |
Pour les développeurs Java, il faut être particulièrement vigilant avec les méthodes virtuelles et leur redéfinition en C# car leur déclaration est à la charge du développeur.
En C#, le mot clé sealed empêche toute redéfinition d'une méthode même marquée avec override.
Exemple C# : |
class ClasseA
{
public virtual void Afficher()
{
Console.WriteLine("ClasseA");
}
}
class ClasseB : ClasseA
{
public override sealed void Afficher()
{
Console.WriteLine("ClasseB");
}
}
class ClasseC : ClasseB
{
public override void Afficher()
{
Console.WriteLine("ClasseC");
}
} |
Résultat : |
'ApplicationTest.ClasseC.Afficher()': cannot override inherited member
'ApplicationTest.ClasseB.Afficher()' because it is sealed
C:\Documents and Settings\jmd\My Documents\Visual Studio 2008\
Projects\ApplicationTest\ApplicationTest\ClasseC.cs
10 30 ApplicationTest |
En Java, le mot clé final empêche toute redéfinition d'une méthode.
Exemple Java : |
public class ClasseA {
public final void afficher() {
System.out.println("ClasseA");
}
}
public class ClasseB extends ClasseA {
public void afficher() {
System.out.println("ClasseB");
}
} |
Résultat : |
C:\java\Test\src\com\jmd\test>javac -cp C:\java\Test\src ClasseB.java
ClasseB.java:5: afficher() in fr.jmdoudoux.dej.ClasseB cannot override afficher() in
fr.jmdoudoux.dej.ClasseA; overridden method is final
public void afficher()
^
1 error |
Java propose le support des generics depuis sa version 5 et C# depuis sa version 2.0.
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
Java propose l'opérateur instanceof pour tester le type d'un objet.
Exemple Java : |
public void tester(Object monObjet)
{
if (monObjet instanceof MaClasse)
{
MaClasse maClasse = (MaClasse) monObjet;
// suite des traitements
}
} |
C# propose l'opérateur is qui est équivalent.
Exemple C# : |
public void Tester(object monObjet)
{
if (monObjet is MaClasse)
{
MaClasse maClasse = (MaClasse) monObjet;
// suite des traitements
}
} |
L'opérateur as en C# permet de demander une conversion vers un autre type. Si la conversion n'est pas possible aucune exception n'est levée et la valeur retournée par l'opérateur est null.
Exemple C# : |
public static void tester(object monObjet)
{
MaClasse maClasse = monObjet as MaClasse;
if (maClasse != null)
{
// suite des traitements
}
} |
Il n'existe pas d'équivalent en Java.
Java et .Net encapsulent les chaînes de caractères dans des objets immuables respectivement java.lang.String et System.String qu'il n'est pas possible de sous-classer. Chaque opération sur ces objets ne modifie pas l'instance courante mais crée une nouvelle instance. Chaque méthode qui modifie le contenu de la chaîne retourne une instance qui contient le résultat des modifications.
Exemple Java : |
String chaine = "test";
chaine.toUpperCase();
System.out.println(chaine); |
Exemple C# : |
string chaine = "test";
chaine.ToUpper();
Console.WriteLine(chaine); |
Dans les deux exemples, la chaîne affichée est en minuscule.
C# propose le mot clé string qui sera remplacé à la compilation par System.String.
Pour gérer des concaténations répétées, Java et .Net proposent respectivement java.lang.StringBuffer (ou java.lang.StringBuilder depuis Java 5) et System.Text.StringBuilder
La taille d'une chaîne est obtenue en utilisant la méthode length() de la classe String en Java et en utilisant la propriété Length de la classe String en C#.
Pour gérer des chaînes de caractères contenant des caractères spéciaux, C# propose de les échapper comme en Java en les faisant précéder d'un caractère antislash ou sans les échapper en faisant précéder la chaîne de caractères par un caractère @ :
Exemple C# : |
string chemin1 = "C:\\temp\\monfichier.txt";
string chemin2 = @"C:\temp\monfichier.txt"; |
En Java, pour tester l'égalité de deux chaines en tenant compte de la casse, il faut utiliser la méthode equals() de la classe String. L'opérateur == appliqué sur deux instances d'un objet de type String teste l'égalité de la référence des objets.
En C#, pour tester l'égalité de deux chaines en tenant compte de la casse, il faut utiliser la méthode equals() de la classe String ou l'opérateur ==.
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
Java et .Net supportent le mécanisme des exceptions pour la gestion des faits inattendus lors de l'exécution des traitements.
La gestion des erreurs est assurée en Java et C# par les mots clés try/catch/finally.
Il est possible en Java et en C# de capturer une exception et de la repropager ou de lever une autre exception.
Les deux plates-formes proposent une hiérarchie de classes d'exceptions standard dérivant de la classe java.lang.Exception pour Java et System.Exception pour C#. Chaque plates-forme permet la définition de ses propres exceptions et proposent le support du chaînage des exceptions (depuis la version 1.4 de Java).
La grande différence dans l'utilisation des exceptions est l'obligation en Java de déclarer, dans la signature des méthodes et grâce au mot clé throws, la propagation d'une exception de type checked non gérée. Les exceptions de type Runtime n'ont pas l'obligation d'être déclarée dans une clause throws.
En C#, il n'y a pas de déclaration des exceptions pouvant être levées dans la signature des méthodes : il n'y a donc pas d'équivalent au mot clé throws de Java. Ainsi l'exception remonte la pile d'appels et si elle n'est pas traitée avant le début de la pile, le CLR s'arrête. Pour compenser ce manque de gestion imposée, il faut documenter les API pour informer les développeurs des exceptions qui peuvent être levées.
En C#, sans le code source ou la documentation associée, il n'est donc pas possible de connaître les exceptions peuvant être levées par une méthode.
|
La suite de cette section sera développée dans une version future de ce document
|
Java et C# proposent tous les deux un support du multitâche au travers de threads
|
La suite de cette section sera développée dans une version future de ce document
|
Java et C# proposent un mécanisme de verrous sur une portion de code pour éviter son exécution simultanée par plusieurs threads. C# propose le mot clé lock et Java le mot clé synchronised : dans les deux langages le mode d'utilisation est le même et repose sur un moniteur d'objets.
Exemple C# : |
lock(this)
{
compteur++ ;
} |
Exemple Java : |
synchronised(this)
{
compteur++ ;
} |
Remarque : le mot clé lock de C# est un raccourci syntaxique à l'utilisation des méthodes Enter() et Exit() de la classe System.Threading.Monitor.
C# propose aussi la classe System.Threading.Interlocked pour synchroniser quelques opérations basiques (incrémentation, décrémentation, échange de valeurs, ajout d'une valeur, ...).
Exemple C# : |
public static int compteur = 0;
public static void incrementer() {
Interlocked.Increment( ref compteur );
} |
Java et C# proposent d'appliquer le mécanisme à une méthode dans son intégralité. Java utilise le mot clé synchronised dans la déclaration de la méthode. C# propose le mot clé interlocked ou la métadonnée MethodImpl avec l'option MethodImplOptions.Synchronized
Exemple C# : |
public static int compteur = 0;
[MethodImpl(MethodImplOptions.Synchronized)]
public static void incrementer() {
compteur++;
} |
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
|
La suite de cette section sera développée dans une version future de ce document
|
C# et Java proposent des mécanismes pour permettre la sérialisation d'objets. La sérialisation est mise en oeuvre notamment dans RMI en Java et .Net Remoting en C#.
|
La suite de cette section sera développée dans une version future de ce document
|
|