C# versus Java

Transcription

C# versus Java
Différences en Java et C#
Apprendre le C# à partir de Java
Contenu
1. Introduction..........................................................................................................................1
2. C# n'est qu'un clone de Java...............................................................................................1
3. La même chose mais en différent.......................................................................................7
4. Comme un air de "Déjà Vu"..............................................................................................20
5. Maintenant, des choses totalement différentes..............................................................37
6. Concepts avancés...............................................................................................................50
7. Conclusion..........................................................................................................................53
1. Introduction
Cet article se propose de vous faire connaître C# à travers un ensemble de
caractéristiques communes ou divergentes de Java. L'auteur de l'article original est Dare
Obasanjo, traduit par Alain Vizzini et adapté par mes soins.
2. C# n'est qu'un clone de Java
De manière explicite, les développeurs du langage C# expliquent qu'ils ont
essentiellement développé leur nouveau langage sur le langage Java, en apportant quelques
modifications et ajouts pour combler ce qu'ils considèrent comme des déficiences. Il existe
donc beaucoup de ressemblance entre les deux langages notamment sur les concepts de base
qui fait qu'il est facile d'apprendre un langage si l'on connaît l'autre.
2.1 Tous des objets !
Comme Java, C# possède une super classe, mère de tous les objets : System.Object. La
classe Java équivalent s'appelle java.lang.object. Les méthodes présentent dans ces deux
classes sont très similaires (p. ex : toString()) excepté wait(), notify() et les autres méthodes
liées à la synchronisation dans la classe Object.
Remarque : En C#, la classe de base peut être écrite soit en minuscule "object" soit en
capital "Object". En fait, à la compilation ces deux types sont remplacés par System.Object.
2.2 Nommage
C# se distingue de Java par le nom des méthodes qui commencent conventionnellement
par une majuscule. Par exemple, la méthode main de Java se nomme main() et Main() en
C#. Le reste des noms utilisent la même convention.
2.3 À propos des machines virtuelles
De la même manière que Java est compilé en byte-code et s'exécute dans un
environnement d'exécution managé (Machine Virtuelle JVM), C# est compilé en MSIL
École Nationale Supérieure d'Ingénieurs
& Groupe de Laboratoires, C.N.R.S.
6, Boulevard Maréchal Juin, F-14050 CAEN cedex
http://www.ensicaen.fr
2 ■ Différences en Java et C#
s'exécutant dans la CRL (Common Langage Runtime). Les deux plate-formes supportent la
compilation Just In Time (JIT). Toutefois, il existe une légère différence entre les deux plateformes, les compilateurs Java permettent de désactiver totalement le JIT en fonctionnant
uniquement en mode interprété alors que les compilateurs .NET en général intègre
nativement le JIT. Enfin, il existe des deux cotés la possibilité de pré-compiler en code natif le
source.
2.4 Gestion de la mémoire (ramasse-miettes)
Tous les objets Java sont créés sur le tas en utilisant le mot-clé new. Il en va de même
pour la plupart des objets C# n'étant pas des types primitifs (ValueType). La libération de la
mémoire n'est pas explicite elle intervient quand le ramasse-miettes le juge utile et récupère
les objets qui ne sont plus utilisés.
2.5 Synthèse des mots-clés
Il y a énormément de similitudes entre les deux langages La table ci-dessous dresse la
correspondance entre les mots-clés.
ENSICAEN - Spécialité Informatique
mot-clé
C#
abstract
as
base
bool
break
byte
case
catch
char
checked
class
const
continue
decimal
default
delegate
mot-clé
Java
abstract
N/A
super
boolean
break
N/A
case
catch
char
N/A
class
const1
continue
N/A
default
N/A
mot-clé
C#
explicit
extern
finally
fixed
float
for
foreach
get
goto
if
implicit
in
int
interface
internal
is
mot-clé
mot-clé
Java
C#
N/A
object
native
operator
finally
out
N/A
override
float
params
for
private
for
protected
N/A
public
goto1
readonly
if
ref
N/A
return
N/A
sbyte
int
sealed
interface
set
protected short
instanceof sizeof
synchronize
stackalloc
d
long
static
do
do
lock
double
double
else
else
enum
event
enum
N/A
long
namespac
package
e
new
new
null
null
mot-clé
Java
N/A
N/A
N/A
N/A
N/A
private
N/A
public
const
N/A
return
byte
final
N/A
short
N/A
mot-clé
mot-clé
C#
Java
this
this
throw
throw
true
true
try
try
typeof
N/A
uint
N/A
ulong
N/A
unchecked N/A
unsafe
N/A
ushort
N/A
using
import
value
N/A
virtual
N/A
void
void
while
while
:
extends
N/A
:
implements
static
N/A
strictfp
string
N/A
N/A
throws
struct
switch
N/A
switch
N/A
volatile
transient
volatile
2.6 Pas de fonctions globales
Comme en Java et contrairement à C++, les méthodes en C# doivent être intégrées dans
une classe.
Différences en Java et C# ■ 3
2.7 Les Interfaces, Oui. L'héritage multiple, Non
C#, comme Java, supporte le concept d'interface qui est assimilé à une classe abstraite
pure. De la même façon, C# et Java autorisent l'héritage multiple d'interface et simple
d'implémentation.
2.8 Les chaînes de caractères ne peuvent être modifiées
C# possède une classe System.String qui est équivalente à java.lang.String. Les deux
classes sont "immuables", c’est-à-dire qu'une fois les objets créés, il n'est pas possible de
modifier leur valeur. Ceci dans le but de protéger le mécanisme d'encapsulation.
Code Java
String jString = "Grapes";
// Ne modifie pas la chaîne, mais retourne une copie
jString.toLowerCase();
Code C#
Pour créer une chaîne de caractères autorisant la modification avec la même référence, il
est conseillé d'utiliser les classes StringBuffer pour Java et StringBuilder pour C#.
Remarque : En C#, la classe chaîne peut-être écrite sous la forme string ou String.
2.9 Les classes non extensibles
Les deux langages proposent des mécanismes consistant à interdire toute extension
d'une classe ; soit par souci d'optimisation, soit par souci de sécurité. Ainsi, il est interdit de
les dériver pour redéfinir des méthodes ou simplement réutiliser l'implémentation. En C#,
on utilise le mot-clé sealed (scellée en français) et en Java le mot-clé final.
Code Java
final class Student {
String fname;
String lname;
int uid;
void attendClass() {}
}
Code C#
sealed class Student {
string fname;
string lname;
int uid;
void AttendClass() {}
}
ENSICAEN - Spécialité Informatique
string csString = "Apple Jack";
// Ne modifie pas la chaîne, mais retourne une copie
csString.ToLower();
4 ■ Différences en Java et C#
2.10 Levée et capture d'exceptions
Les exceptions en C# et Java partagent énormément de caractéristiques. Les deux
langages supportent l'utilisation de l'ordre try pour indiquer qu'un bloc est susceptible de
lever une exception et catch pour capturer l'exception en question. De plus, finally est
implémenté de la même manière pour spécifier qu'une région de code doit, dans tous les cas
être exécutée (exception ou pas). Cela permet de libérer des ressources proprement. Les deux
langages proposent une hiérarchie de classes d'exceptions dérivant d'une super classe :
System.Exception pour C# et java.lang.Exception pour Java. Aussi, il est possible de
chaîner la levée ou la capture d'exception (throw dans un catch) de part et d'autre. Cela
permet, lors de la levée d'une exception, de retourner à l'appelant un type d'exception
correspondant à son contexte et à sa couche d'architecture. Par exemple, une ligne non
trouvée dans une table se traduira par une SQLException que le développeur prendra soin
de renvoyer à l'interface graphique sous la forme d'un ObjectNotFoundException.
Remarque : Cependant, il existe une différence fondamentale entre C# et Java. Comme
en Python, le mot-clé throws n'existe pas en C# car vous n'êtes pas contraint de spécifier
dans la signature d'une méthode le fait qu'elle est susceptible de lever une exception. Il n'y a,
contrairement à Java, aucune vérification de faite à l'exécution.
Code Java
ENSICAEN - Spécialité Informatique
class MyException extends Exception{
public MyException(String message){ super(message); }
public MyException(String message, Exception innerException){
super(message, innerException); }
}
public class ExceptionTest {
static void doStuff(){
throw new ArithmeticException();
}
public static void main(String[] args) throws Exception{
try{
try{
doStuff();
return; // Ne sera pas exécuté
} catch(RuntimeException e) { // parent de ArithmeticException
throw new MyException("MyException levée", e);
}
} finally {
System.out.println("***Block executé même si MyException est
lévée");
}
}
}
Code C#
class MyException: Exception {
public MyException(string message): base(message){ }
public MyException(string message, Exception innerException):
base(message, innerException){ }
}
public class ExceptionTest {
Différences en Java et C# ■ 5
static void DoStuff() {
throw new FileNotFoundException();
}
public static int Main() {
try {
try {
DoStuff();
return 0; // Ne sera pas exécuté
} catch(IOException ioe) { // parent de FileNotFoundException
throw new MyException("MyException levée", ioe);
}
} finally {
Console.WriteLine("***Block executé même si MyException est lévée");
}
}
}
2.11 Initialisation de membres et constructeurs statiques
Code Java
class StaticInitTest {
String instMember = initInstance();
String staMember = initStatic();
StaticInitTest() {
System.out.println("In instance constructor");
}
static {
System.out.println("In static constructor");
}
static String initInstance() {
System.out.println("Initializing instance variable");
return "instance";
}
static String initStatic() {
System.out.println("Initializing static variable");
return "static";
}
static void doStuff() {
System.out.println("Invoking static DoStuff() method");
}
public static void main(String[] args) {
System.out.println("Beginning main()");
StaticInitTest.doStuff();
StaticInitTest sti = new StaticInitTest();
System.out.println("Main() exécuté");
}
}
ENSICAEN - Spécialité Informatique
Les variables d'instance et les variables statiques peuvent être initialisées dès leur
déclaration, et ce, dans les deux langages. Si la variable membre est une variable d'instance,
alors l'initialisation sera effectuée juste avant l'appel du constructeur. Les membres statiques,
eux, sont initialisés dès le chargement de la classe par le Runtime. Cela intervient en règle
générale avant l'appel du constructeur. Il est aussi possible de définir des blocs statiques qui
seront exécutés au chargement. Ils sont appelés plus communément constructeurs statiques.
6 ■ Différences en Java et C#
Code C#
class StaticInitTest {
string instMember = InitInstance();
string staMember = InitStatic();
StaticInitTest() {
Console.WriteLine("In instance constructor");
}
ENSICAEN - Spécialité Informatique
static StaticInitTest() {
Console.WriteLine("In static constructor");
}
static String InitInstance() {
Console.WriteLine("Initializing instance variable");
return "instance";
}
static String InitStatic() {
Console.WriteLine("Initializing static variable");
return "static";
}
static void DoStuff() {
Console.WriteLine("Invoking static DoStuff() method");
}
public static void Main(string[] args) {
Console.WriteLine("Beginning main()");
StaticInitTest.DoStuff();
StaticInitTest sti = new StaticInitTest();
Console.WriteLine("Main() exécuté");
}
}
Exécution des deux exemples :
In static constructor
Beginning main()
Invoking static DoStuff() method
Initializing instance variable
Initializing static variable
In instance constructor
Completed main()
2.12 Boxing
Le boxing (ou emboîtage) est la capacité du langage à traiter des objets comme des types
de valeur de manière implicite. L'équivalent en Java sont les classes Wrappers (Integer, Float,
...) permettant d'encapsuler un type primitif dans une classe afin de le traiter comme un objet
(passage par référence, ...). L'exemple ci-dessous illustre le fonctionnement du boxing de
manière implicite et explicite.
Code Java
class Test {
public static void printString(object o) {
System.out.println("Object: " +o);
}
public static void main(string[] args) {
Point2d p = new Point2d(10, 15);
Différences en Java et C# ■ 7
ArrayList list = new ArrayList();
int z = 100;
printString(p); //p convertit en object
printString(z); //z convertit en object
list.add(1);
list.add(13.12);
list.add(z);
for(int i =0; i < list.Count; i++)
printString(list[i]);
}
}
Code C#
3. La même chose mais en différent
3.1 La méthode Main
Le point d'entrée dans les deux langages est la méthode Main(). Il existe une différence
mineure concernant le nom de la méthode qui commence par un "M" majuscule et les
paramètres utilisés. Ainsi, le main() de C# est surchargé et retourne un code de statut alors
que celui de Java impose une signature bien précise et retourne void.
Code Java
class B {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Code C#
using System;
class A {
public static void Main(String[] args) {
Console.WriteLine("Hello World");
ENSICAEN - Spécialité Informatique
class Test {
public static void PrintString(object o) {
Console.WriteLine("Object: " +o);
}
public static void Main(string[] args) {
Point2d p = new Point2d(10, 15);
ArrayList list = new ArrayList();
int z = 100;
PrintString(p); //p convertit en object
PrintString(z); //z convertiti en object
list.Add(1);
list.Add(13.12);
list.Add(z);
for(int i =0; i < list.Count; i++)
PrintString(list[i]);
}
}
8 ■ Différences en Java et C#
}
}
Il est possible d'avoir deux classes, A et B, contenant toutes deux une méthode main().
En Java, pour spécifier la méthode main() appelée, il suffit de spécifier le nom de la classe
en ligne de commande. Ainsi, en Java, contrairement à C#, il n'est pas nécessaire de
recompiler les classes pour changer le point d'entrée du programme.
En C#, le problème est légèrement différent car le processus de compilation génère un
exécutable pouvant contenir plusieurs Main(). C'est pourquoi, un paramètre de compilation
précisera la méthode par défaut qui sera appelée lors de l'exécution (/main).
Exemple Java
C:\CodeSample> javac A.java B.java
C:\CodeSample> java A
Hello World from class A
C:\CodeSample> java B
Hello World from class B
ENSICAEN - Spécialité Informatique
Exemple C#
C:\CodeSample> csc /main:A /out:example.exe A.cs B.cs
C:\CodeSample> example.exe
Hello World from class A
C:\CodeSample> csc /main:B /out:example.exe A.cs B.cs
C:\CodeSample> example.exe
Hello World from class B
3.2 La syntaxe de l'héritage
C# utilise la syntaxe du C++ pour l'héritage. Le même mot-clé s'utilise pour l'héritage
d'implémentation et d'interface contrairement à Java qui spécifie extends et implements.
Code Java
class B extends A implements Comparable {
int compareTo(){}
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Code C#
class B:A, Icomparable {
int CompareTo(){}
public static void Main(String[] args) {
Console.WriteLine("Hello World");
}
}
Les développeurs Java pourront toujours argumenter que cela rend les sources
Différences en Java et C# ■ 9
difficilement lisibles car le mot-clé indique si la classe mère est une interface ou pas. Pour
palier ce problème, Microsoft définit clairement que les noms d'interfaces doivent
commencer par IMyInterface (tel que Icloneable), mais cela reste un peu insuffisant d'un
point de vue génie logiciel.
3.3 Les Namespaces ou packages
Un Namespace C# est une manière de regrouper des classes par domaine et s'utilise de
manière similaire au mot-clé package en Java. Toutefois, en Java, la structure arborescente
des packages dicte la structure des sources sous-jacentes, ce qui n'est pas le cas de C#.
Les développeurs C++ noteront que la syntaxe des namespaces en C# est très proche de
C++.
Code Java
package com.carnage4life;
public class MyClass {
int x;
void doStuff(){}
}
namespace com.carnage4life {
public class MyClass {
int x;
void DoStuff(){}
}
}
Comme en C++, la syntaxe C# autorise la multiplicité des déclarations de namespaces au
sein d'un même fichier.
Code C#
namespace Company {
public class MyClass { // Company.MyClass
int x;
void DoStuff(){}
}
namespace Carnage4life {
public class MyOtherClass { // Company.Carnage4life.MyOtherClass
int y;
void doOtherStuff(){}
public static void Main(string[] args) {
Console.WriteLine("Hey, I can nest namespaces");
}
}// class MyOtherClass
}// namespace Carnage4life
}// namespace Company
3.4 Accessibilité à la classe
La table ci-dessous synthétise les différents mot-clés permettant de modifier la visibilité
ENSICAEN - Spécialité Informatique
Code C#
10 ■ Différences en Java et C#
et l'accès à une classe dans le but de protéger l'encapsulation. Les fans de C++ déçus lorsque
Sun a modifié la portée de l'instruction protected seront heureux de noter que C# garde la
même sémantique que le C++. Cela signifie donc qu'un membre "protected" ne peut être
accédé que par d'autres méthodes membres situées dans la même classe ou dans une classe
dérivée mais n'est en aucun cas visible de l'extérieur (même package ou non).
Le mot-clé internal signifie que la fonction membre ne peut être accédée que par
d'autres classes situées dans la même assembly. Associée à protected, la visibilité est
étendue aux classes dérivées.
C# access modifier Java access modifier
private
private
public
public
internal
protected
protected
N/A
internal protected N/A
NOTE : La visibilité par défaut d'un champ ou d'une méthode en C# est private alors
qu'en Java elle est protected.
ENSICAEN - Spécialité Informatique
3.5 Constructeurs, Destructeurs et Finaliseurs
La syntaxe et la sémantique des constructeurs en C# est identique à celle de Java. C#
propose aussi le concept de destructeur dans le même esprit que C++ (avec un ~) à ceci près
que la sémantique est strictement la même qu'un finaliseur (Finalize) Java. Bien que les
finaliseurs soient intégrés au langage, il est recommandé de s'en servir avec précaution car il
n'existe aucun contrôle quant à l'ordre dans lequel ils sont appelés. De plus, les objets
possédant des finaliseurs ralentissent le traitement du ramasse-miettes qui est contraint de
les mémoriser plus longtemps dans le mécanisme de libération.
NOTE : En C#, les destructeurs (finalizers) appellent automatiquement les finaliseurs de
leur classe mère contrairement à Java qui impose un appel explicite (super.finalize()).
Code Java
public class MyClass {
static int num_created = 0;
int i = 0;
MyClass() {
i = ++num_created;
System.out.println("Created object #" + i);
}
public void finalize() {
System.out.println("Object #" + i + " is being finalized");
}
public static void main(String[] args) {
for(int i=0; i < 10000; i++)
new MyClass();
}
}
Différences en Java et C# ■ 11
Code C#
using System;
public class MyClass {
static int num_created = 0;
int i = 0;
MyClass() {
i = ++num_created;
Console.WriteLine("Created object #" + i);
}
~MyClass() {
Console.WriteLine("Object #" + i + " is being finalized");
}
public static void Main(string[] args) {
for(int i=0; i < 10000; i++)
new MyClass();
}
}
3.6 Les threads et les membres volatiles
En C#, la création de threads se fait par création d'une instance de la classe
System.Threading.Thread et en passant en paramètre du constructeur un délégué
System.Threading.ThreadStart qui n'est autre qu'un pointeur vers la méthode destinée à
être exécutée. Contrairement à Java qui impose comme point d'exécution d'un thread la
fonction run(), il est ainsi possible en C# de spécifier n'importe quelle fonction.
En Java, toutes les classes héritent des méthodes wait(), notify() and notifyAll()
situées dans java.lang.Object. Les méthodes équivalentes en C# sont Wait(), Pulse()
and PulseAll() se trouvant dans la classe System.Threading.Monitor.
L'exemple ci-dessous déroule le scénario dans lequel des threads sont lancés dans un
ordre bien défini et doivent être traités dans le même ordre. Comme l'exécution des threads
est par nature non-déterministe, ceux qui arriveront après les autres devront attendre leur
tour à l'aide de la méthode wait().
Code Java
class WorkerThread extends Thread {
private Integer idNumber;
private static int num_threads_made = 1;
private ThreadSample owner;
public WorkerThread(ThreadSample owner) {
super("Thread #" + num_threads_made);
idNumber = new Integer(num_threads_made);
num_threads_made++;
this.owner = owner;
}
// dort pendant un temps aléatoire pour simuler l'exécution d'une tâche
public void run() {
Random r = new Random(System.currentTimeMillis());
ENSICAEN - Spécialité Informatique
En Java, les threads sont créés soit par héritage (historique) en dérivation de la classe
java.lang.Thread et en redéfinissant la méthode run() soit par délégation (plus sûre) en
implémentant l'interface java.lang.Runnable.
12 ■ Différences en Java et C#
int timeout = r.nextInt() % 1000;
if(timeout < 0)
timeout *= -1;
try{
Thread.sleep(timeout);
} catch (InterruptedException e){
System.out.println("Thread #" + idNumber + " interrupted");
}
owner.workCompleted(this);
}
public Integer getIDNumber() {return idNumber;}
}
ENSICAEN - Spécialité Informatique
public class ThreadSample{
private Vector threadOrderList = new Vector();
private Integer nextInLine(){
return (Integer) threadOrderList.firstElement();
}
private void removeNextInLine(){
threadOrderList.removeElementAt(0);
// tous les threads ont fini leur travail
if(threadOrderList.isEmpty())
System.exit(0);
}
public synchronized void workCompleted(WorkerThread worker) {
while(worker.getIDNumber().equals(nextInLine())==false) {
try {
// attend la fin d'un thread
System.out.println (Thread.currentThread().getName()
+ " is waiting for Thread #"
+ nextInLine() + " to show up.");
wait();
} catch (InterruptedException e) {}
}
System.out.println("Thread #"+worker.getIDNumber()+" is home free");
// suprimer le thread n° D de la liste des déjà vus
removeNextInLine();
// indique aux autres threads de continuer
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
ThreadSample ts = new ThreadSample();
// Lance 25 threads
for(int i=1; i <= 25; i++) {
ts.threadOrderList.add(new Integer(i));
Thread t = new WorkerThread(ts);
t.start(); // lance les threads
}
Thread.sleep(3600000); // attend la fin de tous les threads
}
}
Code C#
public class WorkerThread {
private int idNumber;
private static int num_threads_made = 1;
private ThreadSample owner;
Différences en Java et C# ■ 13
public WorkerThread(ThreadSample owner) {
idNumber = num_threads_made;
num_threads_made++;
this.owner = owner;
}
// dort pendant un temps aléatoire pour simuler l'exécution d'une tâche
public void PerformTask() {
Random r = new Random((int) DateTime.Now.Ticks);
int timeout = (int) r.Next() % 1000;
if(timeout < 0)
timeout *= -1;
//Console.WriteLine(idNumber + ":A");
try {
Thread.Sleep(timeout);
} catch (ThreadInterruptedException e) {
Console.WriteLine("Thread #" + idNumber + " interrupted");
}
//Console.WriteLine(idNumber + ":B");
owner.workCompleted(this);
}
public int getIDNumber() {return idNumber;}
}
ENSICAEN - Spécialité Informatique
public class ThreadSample {
private static Mutex m = new Mutex();
private ArrayList threadOrderList = new ArrayList();
private int NextInLine() {
return (int) threadOrderList[0];
}
private void RemoveNextInLine() {
threadOrderList.RemoveAt(0);
// tous les threads ont fini leur travail
if(threadOrderList.Count == 0)
Environment.Exit(0);
}
public void workCompleted(WorkerThread worker) {
try {
lock(this) {
while(worker.getIDNumber() != NextInLine()) {
try {
// attend la fin d'un thread
Console.WriteLine ("Thread #"
+ worker.getIDNumber()
+ " is waiting for Thread #"
+ NextInLine() + " to show up.");
Monitor.Wait(this, Timeout.Infinite);
} catch (ThreadInterruptedException e) {} }//while
Console.WriteLine("Thread #"
+ worker.getIDNumber() + " is home free");
// supprime le numero” ID de la liste des thread
// déjà vus
RemoveNextInLine();
// indique à tous les threads de reprendre
Monitor.PulseAll(this);
}
}
}
} catch(SynchronizationLockException){
Console.WriteLine("SynchronizationLockException occurred");
}
14 ■ Différences en Java et C#
}
public static void Main(String[] args) {
ThreadSample ts = new ThreadSample();
// Lance 25 threads
for(int i=1; i <= 25; i++) {
WorkerThread wt = new WorkerThread(ts);
ts.threadOrderList.Add(i);
Thread t = new Thread(new ThreadStart(wt.PerformTask));
t.Start();
}
Thread.Sleep(3600000); // attend tous les threads
}
}
Le mot-clé volatile permet d'interdire aux compilateurs de réaliser certaines
optimisations de code telles que le déplacement des variables du tas vers la pile. Ce genre
d'optimisation, dans la plupart des cas n'a aucun effet nocif sur le déroulement de 99 % de
vos applications, mais dans certaines circonstances, cela peut mener à bien des casses-têtes
difficiles à résoudre.
3.7 Synchronisation de méthodes
ENSICAEN - Spécialité Informatique
3.7.1 Bloc
En Java, il est possible de spécifier explicitement la synchronisation d'un bloc afin
d'éviter que deux threads modifient simultanément la même section critique. C# fournit un
mot-clé lock qui est sémantiquement l'équivalent du synchronized en Java.
Code Java
public void withdrawAmount(int num) {
synchronized(this) {
if(num < this.amount) {
this.amount -= num;
}
}
}
Code C#
public void WithdrawAmount(int num) {
lock(this) {
if(num < this.amount) {
this.amount -= num ;
}
}
}
3.7.2 Méthodes
C# et Java supportent tous les deux les méthodes synchronisées. Lorsqu'une méthode
synchronisée est appelée, le premier thread prend la main sur le moniteur d'objets et bloque
l'accès aux autres threads le temps de l'exécution de la méthode. En Java, les méthodes
synchronisées sont précédées du mot-clé synchronized. En C#, il existe plusieurs manières
d'implémenter la synchronisation de méthodes : par Attributs de Runtime
(MethodImplOptions.Synchronized) ou par utilisation du mot-clé Interlocked.
Différences en Java et C# ■ 15
Code Java
public class BankAccount {
public synchronized void withdrawAmount(int num) {
if(num < this.amount) {
this.amount - num;
}
}
}
Code C#
using System;
using System.Runtime.CompilerServices;
public class BankAccount {
[MethodImpl(MethodImplOptions.Synchronized)]
public void WithdrawAmount(int num) {
if(num < this.amount) {
this.amount – num;
}
}
}
En C#, l'opérateur is est totalement analogue au mot-clé instanceof de Java. Les deux
bouts de code suivant sont équivalents :
Code Java
if (x instanceof MyClass) { MyClass mc = (MyClass) x; }
Code C#
if (x is MyClass) { MyClass mc = (MyClass) x; }
3.9 La « Réflexion »
La capacité à découvrir les méthodes et attributs dans une classe et à invoquer
dynamiquement des méthodes à l'exécution est appelée la réflexion. Cette caractéristique
existe aussi bien en Java qu'en C# à la différence près que la réflexion s'effectue au niveau
d'une « assembly » en C# et au niveau d'une classe en Java.
Code Java
import java.lang.reflect.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
class ReflectionTest {
public static void main(String[] args) {
Class c=null;
Document d;
try {
c = DocumentBuilderFactory.newInstance()
ENSICAEN - Spécialité Informatique
3.8 L'opérateur d'identification de type
16 ■ Différences en Java et C#
.newDocumentBuilder().newDocument().getClass();
d = (Document) c.newInstance();
System.out.println(d + " was created at runtime from its»
+ "Class object");
} catch(ParserConfigurationException pce) {
System.out.println("No document builder exists that can"
+ "satisfy the requested configuration");
} catch(InstantiationException ie) {
System.out.println("Could not create new Document instance");
} catch(IllegalAccessException iae) {
System.out.println("Cannot access default constructor of " + c);
}
// Récupère les methodes de la classe
Method[] methods = c.getMethods();
// affiche la signature des methods et des parametres
for (int i = 0; i < methods.length; i++) {
System.out.println( methods[i]);
Class[] parameters = methods[i].getParameterTypes();
for (int j = 0; j < parameters.length; j++) {
System.out.println("Parameters: "
+ parameters[j].getName());
}
}
ENSICAEN - Spécialité Informatique
}
}
Code C#
using
using
using
using
System;
System.Xml;
System.Reflection;
System.IO;
class ReflectionSample {
public static void Main( string[] args) {
Assembly assembly=null;
Type type=null;
XmlDocument doc=null;
try {
// Charge l'assembly and recupère le type
assembly = Assembly.LoadFrom("C:\\WINNT\\Microsoft.NET"
+"\\Framework\\»+v1.0.2914\\System.XML.dll");
type = assembly.GetType("System.Xml.XmlDocument", true);
// Malheureusement on ne peut pas instancier dynamiquement
// des types via the type object en C#.
doc= Activator.CreateInstance("System.Xml",
"System.Xml.XmlDocument").Unwrap() as XmlDocument;
if(doc != null)
Console.WriteLine(doc.GetType() + " was created at runtime");
else
Console.WriteLine("Could not dynamically create object"
+"at runtime");
} catch(FileNotFoundException) {
Console.WriteLine("Could not load Assembly: system.xml.dll");
return;
} catch(TypeLoadException) {
Console.WriteLine("Could not load Type: System.Xml.XmlDocument"
+ " from assembly: system.xml.dll");
return;
} catch(MissingMethodException) {
Différences en Java et C# ■ 17
Console.WriteLine("Cannot find default constructor of " + type);
} catch(MemberAccessException) {
Console.WriteLine("Could not create new XmlDocument instance");
}
// Récupère les methodes
MethodInfo[] methods = type.GetMethods();
// Affiche la signature des méthodes et des paramètres
for(int i=0; i < methods.Length; i++) {
Console.WriteLine ("{0}", methods[i]);
ParameterInfo[] parameters = methods[i].GetParameters();
for(int j=0; j < parameters.Length; j++) {
Console.WriteLine (" Parameter: {0} {1}"
, parameters[j].ParameterType, parameters[j].Name);
}
}
}
}
Quelque fois, on a besoin d'obtenir les méta-données d'une classe spécifique encapsulée
sous la forme d'un objet. Cet objet est du type java.lang.Class en Java et System.Type en
C#. Ainsi, pour récupérer une instance de cette classe, on utilise getClass() en Java et
GetType() en C#.
Class c = java.util.Arraylist.class; /* Append ".class" to fullname of
class */
Code C#
Type t = typeof(ArrayList);
3.10 Les types primitifs
Pour chaque type primitif en Java, il existe un correspondant en C# avec le même nom,
excepté byte. Le byte en Java est signé et est ainsi proche du type sbyte de C# (et non
byte). De plus, C# possède des versions non signées pour la plupart des types : ulong,
uint, ushort et byte. La seule différence majeure provient du type decimal défini en C#
qui n'effectue aucun arrondi au prix d'un surplus de mémoire et de temps de traitement.
L'équivalent Java est la classe BigDecimal.
Ci-dessous, plusieurs manières d'implémenter des valeurs réelles en C#.
Code C#
decimal dec = 100.44m; //m est le suffixe utilisé pour les décimaux
double dbl = 1.44e2d;
3.11 La déclaration de tableaux
Java possède deux façons de déclarer un tableau : la première, créée pour la compatibilité
avec C et C++ et la deuxième plus simple et plus claire. C# n'utilise que la deuxième façon.
Code Java
int[] iArray = new int[100];
float fArray[] = new float[100];
ENSICAEN - Spécialité Informatique
Code Java
18 ■ Différences en Java et C#
Code C#
int[] iArray = new int[100];
float fArray[] = new float[100]; // ERROR: ne compile pas.
3.12 Déclaration de constantes
Pour déclarer des constantes en Java, vous devez spécifier le mot-clé final. Les variables
"finales" peuvent être initialisées soit à la compilation, soit à l'exécution. Une fois la variable
initialisée, elle devient immuable (non modifiable). Lorsque cette variable est une référence,
la référence ne pourra pointer que sur un seul objet durant sa durée de vie.
Pour déclarer des constantes en C#, le mot-clé const est utilisé pour une initialisation à
la compilation et readonly à l'exécution.
Contrairement à C++, il n'est possible ni en Java ni en C# de spécifier une classe
immuable (équivalent du const MyClass* p).
Code Java
import java.util.*;
ENSICAEN - Spécialité Informatique
public class ConstantTest {
// Constantes définies à la compilation
final int i1 = 10;
static final int i2 = 20;
// constante de classe
// Constante définie à l'exécution
public static final long l1 = new Date().getTime();
// référence d'objet constante
final Vector v = new Vector();
// Constante blanche définie à la première utilisation
final float f;
ConstantTest() {
// définition de la constante au moment de l'instanciation.
if (test) {
f = 17.21f;
} else {
f = 21.12f;
}
}
}
Code C#
using System;
public class ConstantTest {
// Constantes définies à la compilation
const int i1 = 10; // implicitement une variable statique
// La ligne suivante ne compile pas à cause du mot clé 'static'
// public static const int i2 = 20;
// Constante définie à l'exécution
public static readonly uint l1 = (uint) DateTime.Now.Ticks;
// référence d'objet constante
readonly Object o = new Object();
// variable en lecture seule définie à la première utilisation
readonly float f;
Différences en Java et C# ■ 19
ConstantTest() {
// définition de la constante au moment de l'instanciation.
if (test) {
f = 17.21f;
} else {
f = 21.12f;
}
}
}
NOTE : Le langage Java supporte aussi les paramètres de méthode précédés du mot-clé
final. Cela permet aux classes internes déclarées dans la méthode d'accéder aux paramètres
en question. Cette fonctionnalité n'existe pas en C#.
3.13 Chaînage et appel du constructeur père
Code Java
class MyException extends Exception {
private int _id;
public MyException(String message) {
this(message, null, 100);
}
public MyException(String message, Exception innerException) {
this(message, innerException, 100);
}
public MyException( String message,Exception innerException, int id) {
super(message, innerException);
_id = id;
}
}
Code C#
class MyException: Exception {
private int _Id;
public MyException(string message): this(message, null, 100){ }
public MyException(string message, Exception innerException):
this(message, innerException, 100){ }
public MyException(string message, Exception innerException, int id):
base(message, innerException) {
_Id = id;
}
}
ENSICAEN - Spécialité Informatique
Java et C# appellent implicitement les constructeurs parents en cas de création d'un objet.
Les deux langages proposent un moyen d'appeler explicitement le constructeur père en lui
passant des paramètres spécifiques. De plus, Java et C# assurent que l'appel des
constructeurs se fait dans un ordre bien précis garantissant ainsi l'impossibilité d'accéder à
des variables non encore initialisées. Enfin les deux langages intègrent la surcharge de
constructeur et l'appel inter-constructeur afin de réutiliser du code existant. Ce procédé est
aussi appelé "chaînage de constructeurs".
20 ■ Différences en Java et C#
4. Comme un air de "Déjà Vu"
4.1 Les classes imbriquées
En Java et en C# il est possible d'imbriquer des déclarations de classes. En Java, il existe
deux types de classes imbriquées : les classes non statiques connues sous le nom de classe
internes, et les classes statiques. Une classe Java interne peut-être considérée comme une
relation 1,1 avec sa classe englobante.
C# possède l'équivalent des classes statiques Java imbriquées mais n'intègre rien
d'équivalent aux classes internes de Java. Le code suivant nous montre un exemple de classe
imbriquée en C# et Java.
Code Java
public class Car {
private Engine _engine;
private class Engine {
String _make;
}
}
ENSICAEN - Spécialité Informatique
Code C#
public class Car {
private Engine _engine;
private static class Engine {
string _make;
}
}
NOTE : En Java, une classe imbriquée peut-être déclarée dans n'importe quels types de
bloc, ce qui n'est pas le cas de C#. La possibilité de créer des classes imbriquées à l'intérieur
de méthodes peut sembler inutile mais combinée aux classes anonymes, cela peut conduire à
concevoir de puissantes applications basées sur les patrons de conception.
4.2 La surcharge d'opérateurs
La surcharge d'opérateurs autorise la redéfinition de la sémantique d'un opérateur
(+,=,...) dans un contexte particulier. Ainsi, l'égalité de deux chaînes de caractères se résume
à comparer leur valeur. Par défaut, le langage vous fournira une égalité de référence,
difficilement applicable dans ce cas précis.
Cette caractéristique du langage a toujours été source de nombreuses discussions
acharnées de la part des développeurs car son abus peut conduire à divers problèmes. Par
exemple, l'utilisation de la surcharge des opérateurs "++" et "--" dans le cas d'une connexion
ou déconnexion d'une socket réseau peut rendre un programme source difficilement lisible.
Tout programmeur C++ s'est un jour demandé au moment d'utiliser une API, si les
opérateurs d'un type donné étaient redéfinies ou non conduisant au développement d'un
code différent dans un cas ou dans l'autre. D'un autre côté, la surcharge de l'opérateur []
pour renvoyer une copie d'un tableau membre est une alternative intéressante pour garantir
l'encapsulation.
La surcharge d'opérateurs tend à être utile lorsqu'elle est utilisée de manière intuitive en
accord avec le type des classes manipulées. Ainsi, il est judicieux de s'en servir pour les
collections ([]), le calcul matriciel (+ et -), les calculs de nombres complexes ou la
Différences en Java et C# ■ 21
comparaison de types spécifiques induisant la modification de l'égalité sémantique (chaînes,
classes monnaie, ...).
Les développeurs du langage Java ont résolument choisi de ne pas autoriser la surcharge
des opérateurs. En C#, la surcharge d'opérateurs est autorisé, mais contrairement à C++, pas
pour les opérateurs suivants ; new, ( ), ||, &&, =.
Code C#
public class OperatorOverloadingTest {
public static void Main(string[] args) {
OverloadedNumber number1 = new OverloadedNumber(12);
OverloadedNumber number2 = new OverloadedNumber(125);
Console.WriteLine("Increment: {0}", ++number1);
Console.WriteLine("Addition: {0}", number1 + number2);
}
}
4.3 L'instruction "switch"
Il existe deux différences majeures entre l'instruction switch de C# et Java. C# autorise
l'utilisation de chaînes de caractères et interdit le "Fall-through", c’est-à-dire la possibilité
pour un label donné d'exécuter plusieurs lignes lorsque l'instruction "break" est absente. Cela
n'est possible que lorsque le label en question ne propose aucune instruction (cf exemple cidessous). Les "Fall-through" ont été explicitement désactivés en C# car ils sont source
d'erreurs (bugs difficiles à identifier).
Code C#
switch(foo) {
case "A":
Console.WriteLine("A seen");
break;
case "B":
case "C":
Console.WriteLine("B or C seen");
break;
ENSICAEN - Spécialité Informatique
class OverloadedNumber {
private int value;
public OverloadedNumber(int value) {
this.value = value;
}
public override string ToString() {
return value.ToString();
}
public static OverloadedNumber operator -(OverloadedNumber number) {
return new OverloadedNumber(-number.value);
}
public static OverloadedNumber operator +(OverloadedNumber number1,
OverloadedNumber number2) {
return new OverloadedNumber(number1.value + number2.value);
}
public static OverloadedNumber operator ++(OverloadedNumber number) {
return new OverloadedNumber(number.value + 1);
}
}
22 ■ Différences en Java et C#
// ERREUR: Ne compile pas dû l'absence de break entre “D” et “E”.
case "D":
Console.WriteLine("D seen");
case "E":
Console.WriteLine("E seen");
break;
}
4.4 L'instruction "foreach"
La boucle foreach est un moyen efficace et peu verbeux d'effectuer une itération sur une
collection d'objets implémentant l'interface System.Collections.Enumerable en Java ou
System.Collections.IEnumerable en C#. En Java, l'instruction d'itération reprend le mot
clé for des boucles, alors qu'en C# elle utilise le mot foreach.
Code Java
string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"};
for(string str : greek_alphabet) {
System.out.println(str + " is a letter of the greek alphabet");
}
ENSICAEN - Spécialité Informatique
Code C#
string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"};
foreach(string str in greek_alphabet) {
Console.WriteLine(str + " is a letter of the greek alphabet");
}
4.5 Les Assemblies
Les Assemblies en C# ont de nombreux points communs avec les fichiers archives JAR
de Java. Une Assembly est une unité de déploiement primaire contenant un ou plusieurs
Namespaces (ou packages).
Tout comme en Java, les Assemblies contiennent du code MSIL (byte-code), un ensemble
de méta-données et des ressources requises par les classes.
De plus, plusieurs traitements tels que la gestion de la sécurité, le déploiement ou le
versioning sont réalisés au niveau de l'Assembly. En Java, ces configurations s'effectuent au
niveau du fichier ZIP ou JAR ou directement sur l'ensemble des classes non compactées.
Alors que les compilateurs C# génèrent des Assemblies sous la forme de fichiers .DLL ou
.EXE, les exécutables Java sont des archives (JAR ou ZIP).
4.6 Collections
Un certain nombre de langages de programmation populaires contiennent des
Frameworks de gestion de Collections consistant à proposer une implémentation de
structures de données pré-définies (Tableaux, Listes chaînées, Dictionnaires, ...). L'avantage
de ces Frameworks est d'éviter au développeur la tâche consistant à coder tous les
algorithmes de parcours, tri et création de collections.
Dans C#, les collections sont contenues dans le namespace System.Collections. Ce
namespace contient l'ensemble des interfaces et classes représentant des types de données
Différences en Java et C# ■ 23
abstraits tels que IList, IEnumerable, IDictionary, ICollection et CollectionBase. Ces classes
permettent de manipuler des structures de données indépendamment de leur
implémentation. De plus, le namespace System.Collections propose des implémentations
concrètes du type ArrayList, Queue, SortedList et Stack. L'avantage de C# par rapport à Java
concernant les collections réside dans la manière dont est gérée la synchronisation. Ainsi, en
C# il suffit d'utiliser un wrapper synchronisé alors qu'en Java certaines classes sont
synchronisées (Vector, Hashtable , …) et d'autres ne le sont pas (ArrayList, HashMap, …).
Les collections en Java contiennent des classes, interfaces et classes abstraites dans le
package java.util. S'il existe énormément de similitudes avec C#, nous pouvons
considérer que le package Java demeure légèrement plus complet que son homologue en
proposant des caractéristiques supplémentaires. Ainsi, vous trouverez en Java des classes
"ensemblistes" non ordonnées représentées sous l'appellation "set" avec les TreeSet, HashSet,
...
4.7 Goto (plus considéré comme dangereux)
Contrairement à Java, C# contient l'instruction goto qui peut être utilisée pour réaliser
un saut direct vers un label dans un programme.
using System;
using System.Net.Sockets;
class GotoSample {
public static void Main(string[] args) {
int ntries = 0;
retry:
try {
ntries++;
Console.WriteLine(ntries+"attempts to connect to network”);
throw new SocketException();
} catch(SocketException) {
if (ntries < 5) {
goto retry;
}
}
}
}
Pour gérer ce problème dans les boucles, Java utilise des étiquettes.
Code Java
label1:
outer-loop { // e.g. for (int i=0; i<10; i++)
inner-loop {
…
break;
// breaks out the inner loop and ends up in the outer loop
…
continue; // moves back to the beginning of the inner loop
…
continue label1; // breaks out the inner loop and the outer loop
// and continues the outer loop
…
break label1; // breaks out of both loops
…
ENSICAEN - Spécialité Informatique
Code C#
24 ■ Différences en Java et C#
}
4.8 Les méthodes virtuelles (et finales)
Un des concepts les plus importants de la programmation orientée objet est le sacro-saint
polymorphisme. Le polymorphisme vous aide à interagir avec les membres d'une hiérarchie
de classes en utilisant le type le plus générique. Concrètement, cela signifie que vous devez
posséder des méthodes dans la classe de base qui seront ensuite redéfinies dans les classes
dérivées. Par exemple, prenons le cas de la classe MoyenDeLocomotion contenant la fonction
démarrer(), elle-même redéfinie ou implémentée dans la sous-classe Voiture. Le client
utilisant le type générique MoyenDeLocomotion appellera donc la méthode démarrer()
sans se préoccuper du type concret manipulé. La méthode démarrer() dans cet exemple est
appelée méthode virtuelle.
ENSICAEN - Spécialité Informatique
En Java, toutes les méthodes sont virtuelles par défaut, alors qu'en C#, comme en C++, le
développeur doit explicitement l'indiquer. C'est pourquoi, C# propose un mot-clé n'ayant
pas d'équivalent en Java : virtual. Ici encore, cette caractéristique a toujours été source de
moult discussions entre pour et contre. Néanmoins, il faut noter que la nature virtuelle des
méthodes constitue un frein aux optimisations de la machine virtuelle devant prévoir
l'extensibilité de la classe en question. Pour permettre cette optimisation en Java, il faut
adjoindre le mot clé final pour les méthodes non redéfinissables. Une autre raison dans le
choix de C# pourrait s'expliquer par le fait que les méthodes virtuelles peuvent être
accidentellement redéfinies par le développeur dans une sous-classe. Dans ce cas, l'appel à la
méthode de la classe parent sera masqué par la méthode de la classe dérivée et pourrait
engendrer des résultats inexplicables. Toutefois, les IDE permettent aujourd'hui d'éviter ces
erreurs, par exemple en Java en obligeant mettre l'annotation @Override sur une méthode
redéfinie. On peut ainsi détecter le masquage.
Enfin, Java et C# proposent respectivement les mots-clé final et sealed permettant
d'interdire toute redéfinition d'une méthode ou dérivation d'une classe.
Les exemples ci-dessous vous illustrent l'utilisation des méthodes virtuelles en C# et
Java.
Code Java
class Parent {
public void doStuff(String str) {
System.out.println("In Parent.doStuff: " + str);
}
}
class Child extends Parent {
public void doStuff(int n) {
System.out.println("In Child.doStuff: " + n);
}
@Override
public void doStuff(String str) {
System.out.println("In Child.doStuff: " + str);
}
}
public class VirtualTest {
public static void main(String[] args) {
Child ch = new Child();
ch.doStuff(100);
Différences en Java et C# ■ 25
ch.doStuff("Test");
((Parent) ch).doStuff("Second Test");
}
}
Résultat :
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test
Code C#
using System;
public class VirtualTest {
public static void Main(string[] args) {
Child ch = new Child();
ch.DoStuff(100);
ch.DoStuff("Test");
((Parent) ch).DoStuff("Second Test");
}
}
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Parent.DoStuff: Second Test
Le comportement de l'exemple précédent en Java peut être reproduit en C# en marquant
la méthode DoStuff(string) dans la classe mère virtual et en spécifiant dans la méthode
de la classe fille DoStuff(string) le mot-clé override.
Code C#
using System;
public class Parent {
public virtual void DoStuff(string str) {
Console.WriteLine("In Parent.DoStuff: " + str);
}
}
public class Child: Parent {
ENSICAEN - Spécialité Informatique
public class Parent {
public void DoStuff(string str) {
Console.WriteLine("In Parent.DoStuff: " + str);
}
}
public class Child: Parent {
public void DoStuff(int n) {
Console.WriteLine("In Child.DoStuff: " + n);
}
public void DoStuff(string str) {
Console.WriteLine("In Child.DoStuff: " + str);
}
}
26 ■ Différences en Java et C#
public void DoStuff(int n) {
Console.WriteLine("In Child.DoStuff: " + n);
}
public override void DoStuff(string str) {
Console.WriteLine("In Child.DoStuff: " + str);
}
}
public class VirtualTest {
public static void Main(string[] args) {
Child ch = new Child();
ch.DoStuff(100);
ch.DoStuff("Test");
((Parent) ch).DoStuff("Second Test");
}
}
Résultat :
ENSICAEN - Spécialité Informatique
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test
L'exemple précédent peut encore être modifié pour reproduire le résultat initial en
modifiant la signature de la méthode DoStuff(string) dans la classe dérivée ainsi :
public new void DoStuff(string str)
indique que même si la méthode DoStuff de la classe mère est virtuelle, la classe dérivée
peut implémenter "sa" propre version de la méthode sans redéfinir la précédente (elle peut
être appelée ainsi : parent.DoStuff())
4.9 Fichiers Entrée/Sortie
Les deux langages supportent les classes d'entrées/sorties bâties sur le patron de
conception Décorateur. On retrouve pratiquement les mêmes classes. Les exemples cidessous réalisent une copie du contenu d'un fichier appelé "input.txt" vers un autre fichier
"output.txt".
Code Java
import java.io.*;
public class FileIO {
public static void main(String[] args) throws IOException {
File inputFile = new File("input.txt");
File outputFile = new File("output.txt");
FileReader in = new FileReader(inputFile);
BufferedReader br = new BufferedReader(in);
FileWriter out = new FileWriter(outputFile);
BufferedWriter bw = new BufferedWriter(out);
String str;
while((str = br.readLine())!= null)
bw.write(str);
br.close();
bw.close();
}
}
Différences en Java et C# ■ 27
Code C#
using System;
using System.IO;
public class FileIOTest {
public static void Main(string[] args) {
FileStream inputFile = new FileStream("input.txt", FileMode.Open);
FileStream outputFile = new FileStream("output.txt", FileMode.Open);
StreamReader sr
= new StreamReader(inputFile);
StreamWriter sw
= new StreamWriter(outputFile);
String str;
while((str = sr.ReadLine())!= null)
sw.Write(str);
sr.Close();
sw.Close();
}
}
4.10 La Sérialisation
Les objets sérialisables en C# sont marqués par l'attribut de Runtime [Serializable].
L'attribut [NonSerializable] indique que les membres de la classe ne peuvent pas être
sérialisés. Ces membres sont en règle générale des variables temporaires ou des champs
n'ayant aucune signification lorsqu'elles sont persistantes (Thread, ...). C# fournit deux
formats de sérialisation : XML et un format binaire propriétaire.
Vous avez la possibilité d'implémenter votre propre format de sérialisation par
implémentation de l'interface ISerializable.
En Java, les objets sérialisables sont ceux qui implémentent l'interface Serializable
alors que les champs non sérialisables sont marqués avec le mot-clé transient. Par défaut,
Java supporte la sérialisation d'objets sous un format binaire propriétaire mais fournit,
comme en C#, un mécanisme permettant soit d'étendre le format initial ( readObject et
writeObject), soit de ré-écrire entièrement le processus de sérialisation (interface
Externalizable).
private void readObject(java.io.ObjectInputStream stream) throws
IOException, ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream) throws
IOException
Code Java
import java.io.*;
ENSICAEN - Spécialité Informatique
La persistance d'objets, connue aussi sous le terme sérialisation est la caractéristique
consistant à lire et à écrire l'état d'un objet (la valeur de ses attributs) dans un flux
quelconque (fichier texte, socket réseau, ...). La sérialisation est utile lorsqu'il s'agit de
sauvegarder l'état d'un graphe d'objets entre plusieurs invocations de méthodes. Vous êtes
en mesure de reconstruire entièrement l'état de votre application suite à un crash ou tout
autre arrêt inopiné. De plus, la sérialisation vous permet de faire transiter des objets à travers
le réseau dans le but de transférer des objets entre plusieurs applications. Ce mécanisme est
fortement utilisé dans les architectures distribuées avec RMI, CORBA ou encore .NET
Remoting.
28 ■ Différences en Java et C#
ENSICAEN - Spécialité Informatique
class SerializeTest implements Serializable {
transient int x;
private int y;
public SerializeTest(int a, int b) {
x = a;
y = b;
}
public String toString(){
return "{x=" + x + ", y=" + y + "}";
}
public static void main(String[] args) throws Exception{
SerializeTest st = new SerializeTest(66, 61);
System.out.println("Before Write := " + st);
System.out.println("\n Writing SerializeTest object to disk");
FileOutputStream out = new FileOutputStream("serialized.txt");
ObjectOutputStream so = new ObjectOutputStream(out);
so.writeObject(st);
so.flush();
System.out.println("\n Reading SerializeTest object from disk\n");
FileInputStream in
= new FileInputStream("serialized.txt");
ObjectInputStream si
= new ObjectInputStream(in);
SerializeTest fromdisk = (SerializeTest)si.readObject();
// x sera 0 parce qu'il ne sera pas lu puisqu'il est transient
System.out.println("After Read := " + fromdisk);
}
}
Code C#
using
using
using
using
using
using
System;
System.IO;
System.Reflection;
System.Runtime.Serialization;
System.Runtime.Serialization.Formatters.Binary;
System.Runtime.Serialization.Formatters.Soap;
[Serializable]
class SerializeTest {
[NonSerialized]
private int x;
private int y;
public SerializeTest(int a, int b) {
x = a;
y = b;
}
public override String ToString() {
return "{x=" + x + ", y=" + y + "}";
}
public static void Main(String[] args) {
SerializeTest st = new SerializeTest(66, 61);
Console.WriteLine("Before Binary Write := " + st);
Console.WriteLine("\n Writing SerializeTest object to disk");
Stream output = File.Create("serialized.bin");
BinaryFormatter bwrite = new BinaryFormatter();
bwrite.Serialize(output, st);
output.Close();
Console.WriteLine("\n Reading SerializeTest object from disk\n");
Stream input = File.OpenRead("serialized.bin");
BinaryFormatter bread = new BinaryFormatter();
Différences en Java et C# ■ 29
SerializeTest fromdisk = (SerializeTest)bread.Deserialize(input);
input.Close();
// x sera 0 parce qu'il ne sera pas lu puisqu'il est non-serialized
Console.WriteLine("After Binary Read := " + fromdisk);
st = new SerializeTest(19, 99);
Console.WriteLine("\n\nBefore SOAP(XML) Serialization := " + st);
Console.WriteLine("\n Writing SerializeTest object to disk");
output = File.Create("serialized.xml");
SoapFormatter swrite = new SoapFormatter();
swrite.Serialize(output, st);
output.Close();
Console.WriteLine("\n Reading SerializeTest object from disk\n");
input = File.OpenRead("serialized.xml");
SoapFormatter sread = new SoapFormatter();
fromdisk = (SerializeTest)sread.Deserialize(input);
input.Close();
// x sera 0 parce qu'il ne sera pas lu puisqu'il est non-serialized
Console.WriteLine("After SOAP(XML) Serialization := " + fromdisk);
Console.WriteLine("\n\nPrinting XML Representation of Object");
XmlDocument doc = new XmlDocument();
doc.Load("serialized.xml");
Console.WriteLine(doc.OuterXml);
}
}
Java et C# proposent tous les deux un mécanisme de génération de documentation à
partir des sources. Il suffit de respecter une certaine forme d'écriture des commentaires,
lesquels seront analysés (parsing) par un outil afin de produire une documentation sous la
forme de fichiers texte. Le langage Java a été le premier à intégrer cette technique et C# s'en
est inspiré.
Javadoc est l'outil utilisé pour extraire à partir des sources l'ensemble de la
documentation. Il génère du code HTML à partir des commentaires du programme. Le
meilleur exemple d'utilisation de Javadoc est la documentation du JDK.
Les différentes méta-données pouvant être spécifiées sont les suivantes :
► La description de la méthode
► Les exceptions levées par la méthode.
► Les paramètres acceptés
► Les paramètres de retour.
► Les méthodes associées et attributs membres.
► Une indication sur le caractère obsolète de la méthode (deprecated).
► La version de l'API dans laquelle elle a été créée.
L'information sur le caractère obsolète d'une méthode est aussi utilisée par le
compilateur pour générer des messages d'avertissement ou d'erreurs.
Javadoc fournit automatiquement les informations suivantes :
► Les API héritées
► La liste des classes dérivées
► Un classement hiérarchique, alphabétique de vos classes
ENSICAEN - Spécialité Informatique
4.11 Génération de la documentation à partir du code source
30 ■ Différences en Java et C#
► Un index contenant la liste des méthodes et variables utilisées
Code Java
/**
* Calculates the square of a number.
* @param num the number to calculate.
* @return the square of the number.
* @exception NumberTooBigException this occurs if the square
* of the number is too big to be stored in an int.
*/
public static int square(int num) throws NumberTooBigException{}
C# utilise XML comme format pour la génération de documentation. Les fichiers
contiennent les méta-données spécifiées par l'utilisateur ainsi que des informations
supplémentaires générées automatiquement. Il existe très peu de différences entre les types
de méta-données C# et Java à quelques exceptions près ( @author, @version, ou
@deprecated, ...).
Code C#
ENSICAEN - Spécialité Informatique
///<summary>Calculates the square of a number.</summary>
///<param name="num">The number to calculate.</param>
///<return>The square of the number. </return>
///<exception>NumberTooBigException - this occurs if the square
/// of the number is too big to be stored in an int. </exception>
public static int square(int num){}
4.12 Plusieurs classes dans un même fichier
Il est possible dans les deux langages d'inclure plusieurs déclarations de classes dans le
même fichier avec plusieurs différences notables. Ainsi, en Java il ne peut y avoir qu'une
seule classe de visibilité publique par fichier et elle doit posséder absolument le même nom
que le fichier en question. Ces contraintes n'existent pas en C#, vous pouvez définir plusieurs
classes publiques et leur donner un nom quelconque sans rapport avec le fichier.
4.13 L'import de bibliothèques
L'utilisation d'une bibliothèque dans une application est une opération à deux étapes. La
première étape consiste à référencer la bibliothèque dans le programme source en utilisant
les directives using pour C# et import pour Java. La deuxième étape consiste à indiquer au
compilateur le chemin des dites bibliothèques. En Java, cette opération se fait à l'aide de la
variable d'environnement CLASSPATH ou d'une option du compilateur : -classpath. En C#,
c'est l'option du compilateur /r qui est utilisée.
4.14 Les événements
Le modèle de programmation événementielle consiste à mettre en place un mécanisme
permettant de notifier des objets abonnés à un événement particulier. Ce modèle est souvent
associé au design pattern observer/observable et s'utilise en général dans la conception
d'interfaces graphiques (IHM). Java et C# proposent tous les deux des mécanismes
supportant les événements mais avec une implémentation totalement différente. Ainsi, en
Java, java.util.EventObject est la classe parent de tous les événements et possède un
ensemble de méthodes permettant d'identifier la source de l’événement. Ensuite, un abonné
appelé Listener, identifié à l'aide d'interfaces existantes, devra implémenter une méthode dite
de « callback ». Java traite la gestion d’événements avec un typage fort. Vous trouverez donc
Différences en Java et C# ■ 31
dans les APIs MouseListener, ActionListener, KeyListener, .. contenant toutes des méthodes
de callback en relation avec la sémantique de l'évènement (keyPressed, actionPerformed, ...)
C# utilise les délégués (voir section 38) pour fournir un mécanisme explicite permettant
de gérer l'abonnement/notification. Les délégués sont concrètement des pointeurs de
fonction et un événement est généralement un type dérivé de System.EventArgs. Ainsi,
lorsqu'un événement apparaît, il suffit de passer au gestionnaire abonné la source et
l’événement en question de la manière suivante : new YourEventArgs(inits). L'émetteur
possède une méthode protected précédée en général de "On" (e.g OnClick, OnClose,
OnInit, etc...) invoquée lorsqu'un événement spécifique apparaît. Cette méthode redirige
ensuite l'appel au délégué spécifique en passant l'objet EventArgs préalablement initialisé.
Les méthodes "OnXX" sont de visibilité protected pour permettre à une classe dérivée d'y
faire appel. L'abonné est une méthode (contrairement à Java où c'est une classe) acceptant les
mêmes arguments et paramètres de retour que le délégué. En règle générale, les délégués
possèdent deux arguments : Object pour l'objet source et EventArgs pour le contenu de
l’événement, et void comme paramètre de retour.
En C#, le mot-clé event spécifie un type particulier de délégué : ceux chargés de traiter
des événements. Pendant la phase de compilation, le compilateur surcharge les opérateurs
+= et -= afin de simplifier l'opération d'enregistrement ou de suppression d'un callback. En
Java, il vous faut passer par des méthodes prévues à cet effet : addActionListener() et
L'exemple ci-dessous vous illustre une classe générant 20 nombres aléatoires et
produisant un événement à chaque fois qu'un nombre pair est généré.
Code Java
import java.util.*;
class EvenNumberEvent extends EventObject {
public int _number;
public EvenNumberEvent(Object source, int number) {
super(source);
_number = number;
}
}
interface EvenNumberSeenListener {
void evenNumberSeen(EvenNumberEvent ene);
}
class Publisher {
Vector _subscribers = new Vector();
private void OnEvenNumberSeen(int num) {
for(int i=0, size = _subscribers.size(); i < size; i++)
((EvenNumberSeenListener)subscribers.get(i)).evenNumberSeen(new
EvenNumberEvent(this, num));
}
public void addEvenNumberEventListener(EvenNumberSeenListener l) {
_subscribers.add(l);
}
public void removeEvenNumberEventListener(EvenNumberSeenListener l) {
_subscribers.remove(l);
}
// genere 20 nombres aléatoires entre 1 et 20 et then genere un
// evenement si le nomber est paire.
public void RunNumbers() {
Random r = new Random(System.currentTimeMillis());
ENSICAEN - Spécialité Informatique
RemoveActionListener().
32 ■ Différences en Java et C#
for(int i=0; i < 20; i++) {
int current = (int) r.nextInt() % 20;
System.out.println("Current number is:" + current);
if((current % 2) == 0)
OnEvenNumberSeen(current);
}
}
}
public class EventTest implements EvenNumberSeenListener {
// callback function that will be called when even number is seen
public void evenNumberSeen(EvenNumberEvent e)
System.out.println("\t\tEven Number Seen:" +
((EvenNumberEvent)e).number);
}
public static void main(String[] args) {
EventTest et = new EventTest();
Publisher pub = new Publisher();
pub.addEvenNumberEventListener(et);
pub.RunNumbers();
pub.removeEvenNumberEventListener(et);
}
}
ENSICAEN - Spécialité Informatique
Code C#
using System;
class EvenNumberEvent: EventArgs {
// Remarque: en pratique l'attribut serait une propriété
internal int _number;
public EvenNumberEvent(int number):base() {
_number = number;
}
}
class Publisher {
public delegate void EvenNumberSeenHandler(object sender, EventArgs e);
public event EvenNumberSeenHandler EvenNumHandler;
protected void OnEvenNumberSeen(int num) {
if(EvenNumHandler!= null)
EvenNumHandler(this, new EvenNumberEvent(num));
}
// genere 20 nombres aléatoires entre 1 et 20 et then genere un
// evenement si le nomber est paire.
public void RunNumbers() {
Random r = new Random((int) DateTime.Now.Ticks);
for(int i=0; i < 20; i++) {
int current = (int) r.Next(20);
Console.WriteLine("Current number is:" + current);
if((current % 2) == 0)
OnEvenNumberSeen(current);
}
}
}
public class EventTest {
public static void EventHandler(object sender, EventArgs e) {
Console.WriteLine("\t\tEven Number Seen:"
+ ((EvenNumberEvent)e).number);
Différences en Java et C# ■ 33
}
public static void Main(string[] args) {
Publisher p = new Publisher();
p.EvenNumHandler+=new Publisher.EvenNumberSeenHandler(EventHandler);
pub.RunNumbers();
p.EvenNumHandler-=new Publisher.EvenNumberSeenHandler(EventHandler);
}
}
4.15 Interopérabilité inter-language
L'interopérabilité inter-langages est la capacité à intégrer plusieurs applications écrites
dans différents langages.
Il existe plusieurs manières de ré-utiliser du code C/C++ en Java. Tout d'abord, JNI (Java
Native Interface) a été créé dans ce but. Les classes natives (non Java) appelées doivent
respecter un certain modèle de développement utilisant des types bien spécifiques propres à
l'API JNI. Pour créer une telle application, il vous faut respecter les étapes suivantes :
1/ Créer un programme Java contenant une méthode identifiée par le mot-clé native.
2/ Charger la bibliothèque correspondante au code C/C++ compilé à l'aide de la méthode
loadLibrary().
4/ Utiliser le générateur de stubs javah avec l'option -jni afin de générer les ".h"
nécessaires aux invocations.
5/ Coder l'implémentation de la méthode native (généralement du C ou du C++).
6/ Compiler les fichiers ".h" et les fichiers source dans une bibliothèque partagée (.dll,.so, …).
Cette méthode assure que le code source Java est totalement indépendant de la
bibliothèque native utilisée. Ainsi, tout portage vers d'autres plate-formes s'en trouve
simplifié.
Java propose aussi la possibilité d’interagir avec un système distribué via un ORB
(Object Request Broker) tel que Corba ou Rmi. Le middleware est responsable d'assurer la
partie communication et marshalling des paramètres entre un objet Java et un objet C++.
Sans chercher aussi loin, les WebServices avec Soap sont aussi une solution à ce style de
problème. Bien entendu, ces techniques supposent de mettre en place une mécanique assez
lourde et parfois inutile lorsque les deux applications n'ont aucune vocation à être
distribuées.
Avec une interopérabilité binaire, les objets sont en mesure d'interagir entre eux de
manière plus intégrée. Ainsi, il est possible d'hériter de l'interface ou de la classe d'un objet
écrit dans un autre langage ou de partager les mêmes types. Les compilateurs et autres
débogueurs se basent uniquement sur le byte-code généré qui est le même pour tous les
langages. C'est l'approche de .NET.
La CLR (Common Language Specification) se charge de l'environnement d'exécution et
la CLS assure l'homogénéité du code binaire exécuté (MSIL) à travers un ensemble de types
unifiés (CTS). Il existe à l'heure actuelle plus d'une dizaine de compilateurs proposant la
génération de code binaire .NET.
Un autre aspect de l'interopérabilité inter-langage supporté par C# est l'interaction avec
les objets COM. Le Framework propose des mécanismes permettant d'appeler simplement
un objet COM à partir de code C#. Il suffit d'utiliser des outils fournis par le Framework et
ENSICAEN - Spécialité Informatique
3/ Compiler la classe contenant la déclaration des méthodes natives.
34 ■ Différences en Java et C#
permettant de générer des classes .NET Proxy prenant en charge le passage entre les deux
mondes (tlbimp et tlbexp).
Les programmes C# peuvent aussi appeler une fonction située dans n'importe quelle
DLL en utilisant le mot-clé extern associé à l'attribut DllImport. L'avantage de cette
technique est que la méthode de la DLL en question n'a pas besoin d'être nécessairement
écrite pour C#. L'inconvénient réside dans le caractère non portable de cette technique
puisque l'invocation est fortement liée au format binaire des DLL Win32 spécifiques à
Microsoft. Aussi, c'est un autre moyen simple de ré-utiliser du code "non managé" sans
passer par des wrappers.
Indéniablement, C# propose plus d'alternatives que Java concernant l'intégration et
l'interopérabilité avec les applications développées dans d'autres langages. D'un autre côté,
Java insiste sur le caractère portable de son architecture et rejette totalement toute instruction
liée à une plate-forme spécifique. Mais, il apparaît de plus en plus que le cloisonnement de la
plate-forme Java autour d'un seul langage est une stratégie de moins en moins payante car la
communauté de développeurs ne semble pas encore prête à accepter le monopole d'un seul
langage. Techniquement, il est tout à fait envisageable d'intégrer la compilation de code C#,
C++ ou Eiffel en byte-code Java, mais Sun le veut-il vraiment ?
4.16 Attributs
ENSICAEN - Spécialité Informatique
Les attributs de Runtime fournissent un moyen élégant d'insérer des annotations (i.e
méta-données) dans un module, une classe, une méthode, un paramètre ou une variable
membre. Les attributs sont très populaires dans un certain nombre de langages de
programmation tels que le noyau Linux ou les objets COM (Component Object Model). Ces
attributs sont un moyen de fournir des informations au runtime dans le but d'exécuter des
tâches additionnelles ou d'étendre les caractéristiques d'un type donné.
Les quelques exemples ci-dessous vous illustrent les différents types attributs pouvant
être rencontrés en C# :
► [MethodImpl(MethodImplOptions.Synchronized)] :
synchronized de Java.
Similaire
au
mot-clé
► [Serializable] : Similaire à l'implémentation de l'interface java.io.Serializable
de Java.
► [WebMethod] : utilisé en combinaison avec ASP.NET permet de spécifier qu'une
méthode est un web service.
En Java, les Attributs sont implémentés par des annotations. Leur syntaxe utilise le métacaractère @. Les annotations les plus courantes sont :
► @Override : déclare qu'une méthode redéfinie une autre.
► @SuppressWarnings("unchecked") : pour supprimer les messages d'alerte du
compilateur.
► Toutes les annotations utilisées par JUnit et tous les frameworks JEE.
Code C#
//declaration of bit field enumeration
[Flags]
enum ProgrammingLanguages{
C
= 1,
Lisp = 2,
Différences en Java et C# ■ 35
Basic = 4,
All
= C | Lisp | Basic
}
aProgrammer.KnownLanguages = ProgrammingLanguages.Lisp; //set known
languages ="Lisp"
aProgrammer.KnownLanguages |= ProgrammingLanguages.C; //set known
languages ="Lisp C"
aProgrammer.KnownLanguages &= ~ProgrammingLanguages.Lisp; //set known
languages ="C"
if((aProgrammer.KnownLanguages & ProgrammingLanguages.C) > 0){ //if
programmer knows C
//.. do something
}
Il est possible d'accéder aux attributs d'un module, d'une classe ou d'une méthode par
l'intermédiaire des APIs de reflection. C'est une fonctionnalité très utile pour vérifier à
l'exécution si une classe supporte un type d'attribut spécifique ou pour extraire des valeurs
données.
Code C#
using System.Reflection;
[AttributeUsage(AttributeTargets.Class)]
public class AuthorInfoAttribute: System.Attribute {
string author;
string email;
string version;
public AuthorInfoAttribute(string author, string email) {
this.author = author;
this.email = email;
}
public string Version {
get {
return version;
}
set {
version = value;
}
}
public string Email {
get {
return email;
}
}
public string Author {
get {
return author;
}
}
}
[AuthorInfo("Dare Obasanjo", "[email protected]", Version="1.0")]
ENSICAEN - Spécialité Informatique
Les développeurs peuvent créer leurs propres attributs dans les deux langages. Par
exemple, en XC# il faut dériver la classe System.Attribute.L'exemple suivant nous
comment implémenter son propre attribut fournissant des informations sur l'auteur des
sources et comment récupérer les valeurs correspondantes à l'exécution.
36 ■ Différences en Java et C#
class HelloWorld { }
class AttributeTest {
public static void Main(string[] args){
/* Get Type object of HelloWorld class */
Type t = typeof(HelloWorld);
Console.WriteLine("Author Information for " + t);
Console.WriteLine("=================================");
foreach(AuthorInfoAttribute att in
t.GetCustomAttributes(typeof(AuthorInfoAttribute), false)) {
Console.WriteLine("Author: " + att.Author);
Console.WriteLine("Email: " + att.Email);
Console.WriteLine("Version: " + att.Version);
}//foreach
}//Main
}
4.17 Les listes variables de paramètres
En C et C++, il est possible de spécifier qu'une fonction prend un nombre variable de
paramètres, cette fonctionnalité est fortement utilisée dans le cadre des fonctions printf()
et scanf(). Cela est aussi possible en Java et en C#.
ENSICAEN - Spécialité Informatique
En Java, cette nouvelle fonctionnalité implique une nouvelle notation pour préciser la
répétition d'un type d'argument. Cette nouvelle notation utilise trois petits points : ...
public class TestVarargs {
public static void main(String[] args) {
System.out.println("valeur 1 = " + additionner(1,2,3));
System.out.println("valeur 2 = " + additionner(2,5,6,8,10));
}
public static int additionner(int ... valeurs) {
int total = 0;
for (int val : valeurs) {
total += val;
}
return total;
}
}
En C#, le fonctionnement est le même excepté qu'il faut spécifier le mot-clé param pour
indiquer que le nombre de paramètres est variable.
Code C#
class ParamsTest {
public static void PrintInts(string title, params int[] args) {
Console.WriteLine(title + ":");
foreach(int num in args)
Console.WriteLine(num);
}
public static void Main(string[] args) {
PrintInts("First Ten Numbers in Fibonacci Sequence", 0, 1, 1, 2, 3, 5,
8, 13, 21, 34);
}
}
Différences en Java et C# ■ 37
5. Maintenant, des choses totalement différentes
5.1 La libération déterministe d'objets (Dispose Design Pattern)
Pour fournir un contrôle total sur la libération des ressources utilisées par les objets, C#
propose l'interface System.IDisposable contenant la méthode Dispose() pouvant être
appelée directement par un utilisateur lorsqu'il décide de libérer une ressource. L'utilisateur
peut ainsi spécifier de manière explicite qu'il désire libérer des objets qui ne sont plus
utilisés. Cette technique est à différencier de la Finalisation proposée par Java et C# qui
n'assure pas une libération "déterministe" des objets. Concrètement, le fait d'appeler la
méthode Dispose() invoque la méthode GC.SuppressFinalisation(this) qui supprime
l'objet de la file de finalisation. Ainsi, l'objet peut-être libéré sans avoir à attendre que le
Thread de finalisation se lance.
Si une classe est "disposable", il est préférable de faire usage de la méthode Dispose()
une et une seule fois. Ainsi, de multiples appels successifs à Dispose() n'auront aucun effet.
Vous pouvez implémenter un mécanisme basé sur des flags permettant de contrôler le fait
que la classe ait été déjà libérée. L'exemple ci-dessous nous illustre un programme dans
lequel une classe référence un fichier ouvert jusqu'à ce que la méthode Dispose() soit
appelée.
using System;
using System.IO;
public class MyClass : IDisposable {
bool disposed = false;
FileStream f;
StreamWriter sw;
private String name;
private int numShowNameCalls = 0;
MyClass(string name) {
f = new FileStream("logfile.txt", FileMode.OpenOrCreate);
sw = new StreamWriter(f);
this.name = name;
Console.WriteLine("Created " + name);
}
~MyClass() {
Dispose(false);
}
public void Dispose() {
if(!disposed) {
Dispose(true);
}
}
private void Dispose(bool disposing) {
lock(this) {
// empeche plusieurs threads d'entrer ici en meme temps.
if(disposing) {
Console.WriteLine("Finalizing " + name);
sw.Close();
GC.SuppressFinalize(this);
disposed = true;
}
}
}
ENSICAEN - Spécialité Informatique
Code C#
38 ■ Différences en Java et C#
public string ShowName() {
if(disposed)
throw new ObjectDisposedException("MyClass");
numShowNameCalls++;
sw.Write("ShowName() Call #" + numShowNameCalls.ToString() + "\n");
return "I am " + name;
}
public static void Main(string[] args) {
using (MyClass mc = new MyClass("A MyClass Object")) {
for(int i = 0; i < 10; i++) {
Console.WriteLine(mc.ShowName());
}
} // appel de Dispose() de l'objet MyClass des que le bloc
// de code est terminé, meme si une exception est levée
}
}
Ce mécanisme est très proche des destructeurs de C++ et permet de ne pas se soucier de
la gestion des allocations mémoire.
La libération déterministe d'objet est une avancée significative dans une prise en charge
plus fine des ramasse-miettes.
ENSICAEN - Spécialité Informatique
NOTE : Attention, l'appel à la méthode Dispose() ne demande pas directement au
ramasse-miettes de libérer l'objet mais accélère ce processus en outre-passant l'étape de
finalisation.
5.2 Les Délégués
Les délégués du C# sont équivalents aux pointeurs de fonction du C/C++ mais en plus
sécurisé (type-safe). En simplifiant, on peut dire qu’un délégué est un pointeur vers une
méthode mais c’est bien plus que cela.
Un délégué est un intermédiaire entre un émetteur et un récepteur, il permet d’établir un
contrat entre eux. C’est un moyen de communication entre objets qui ne se connaissent pas
forcément. Les délégués sont très utilisés dans l’environnement .NET, notamment le
mécanisme de gestion des événements qui est directement basé sur les délégués ; un
événement est d’ailleurs un délégué spécialisé.
La notion de délégué pourrait être comparé à celle d’interface puisque une interface est
aussi un contrat entre classes sauf qu’un délégué n’établit son contrat qu’avec une méthode
et non une classe. En quoi consiste le contrat d’un délégué ? Il consiste lors de la déclaration
de celui-ci à spécifier complètement une méthode : ses paramètres, leur nombre, leur type et
le type de retour de la méthode.
5.2.1 Quel est l’intérêt d’utiliser un délégué ?
L’intérêt des délégués n’est pas forcément visible au premier abord. Cependant, ils
répondent à un problème récurrent en programmation : Fournir un traitement via une
méthode à un objet qui l’exécutera plus tard (traitement asynchrone), c'est-à-dire que l’objet
appellera ce code au bon moment et ce sans forcément le connaître. Par exemple, une
Différences en Java et C# ■ 39
application exécute un objet chargé de gérer des communications via le réseau utilisera un
délégué pour signaler qu’une donnée vient d’arriver et est disponible.
Ce mécanisme que les délégués utilisent de type « rappelle-moi plus tard » (système de
rappel, callback en anglais) permet de gérer aisément les traitements asynchrones mais offre
aussi d’autres avantages. Dans l’exemple précédent, la classe communication ne connaît rien
de l’application qui l’utilise, le code de cette classe est indépendant et réutilisable dans
d’autres situations sans aucune modification. De manière générale, l’utilisation des délégués
permet de créer des classes, des contrôles ou composants utilisateurs indépendants les uns
des autres (dit à couplage faible) ce qui facilite la réutilisation du code et tout ce qui en
découle : maintenance, évolution & publication.
5.2.2 Exemple
Un délégué est un type, une signature de méthode, qu’il faut définir. Utiliser un délégué
nécessite de déclarer une variable du type du délégué à utiliser. Le délégué déclaré cidessous défini une signature de méthode appelée DataReceived, pleinement précisée : la
méthode comporte un paramètre de type chaîne de caractères, la valeur de retour est de type
entier.
// définition d’un type délégué
delegate int DataReceivedHanlder(data);
// le délégué, initialisé à null
DataReceivedHandler dataReceivedHandler=null;
L’utilisation d’un délégué passe par plusieurs phases : la connexion ou l’abonnement, le
déclenchement puis la notification. L’abonnement consiste à instancier le délégué et à le
relier à une méthode dans la classe qui utilise l’objet ayant le délégué.
// instancie l'objet pour communiquer
comm = new Communication();
// instancie le délégué et le relie à une méthode de l’application
comm.dataReceivedHandler = new
Communication.DataReceivedHandler(DonneeRecue);
Le déclenchement du délégué consiste à l’appeler comme un simple appel de méthode
lorsque c’est nécessaire.
ENSICAEN - Spécialité Informatique
La classe communication est chargée de gérer les communications réseaux, d’envoyer et
de recevoir des données. La réception des données est indépendante du fonctionnement de
l’objet, elle ne dépend que des émetteurs. Elle sera gérée par un délégué qui sera associé à
une méthode dans l’application. Cette méthode affichera la donnée reçue.
40 ■ Différences en Java et C#
// appel délégué pour signaler présence donnée recue
if( dataReceivedHandler != null) {
// appel si délégué est connecté
dataReceivedHandler(data);
}
La notification est le fait que le délégué déclenche automatiquement lorsqu’il est appelé
la méthode associée dans l’application.
// déclenché par le délégué lorsque une donnée est recue
private void DonneeRecue(string data) {
// affiche le message recu
MessageBox.Show("donnée recue: "+data);
}
Le délégué a fait son travail d’intermédiaire, il relaye la notification à la méthode associé
dans l’application.
Code de l’exemple « Communication » commenté
Ci-dessous, se trouve les extraits de codes principaux utilisé pour expliquer le
fonctionnement des délégués.
ENSICAEN - Spécialité Informatique
Classe communication
public class Communication {
// déclaration type délégué
public delegate int DataReceivedHandler(string data);
// variable du type délégué donnée recue
public DataReceivedHandler dataReceivedHandler=null;
// traitement d'attente en réception des données
// effectué dans une boucle, exécuté par un thread
public void RunReceptionData() {
string data= null;
// boucle d'attente
while(true) {
// donnée en reception
// ... remplir variable data
// appel du délégué pour signaler présence donnée recue
if( dataReceivedHandler != null)
dataReceivedHandler(data);
// suite traitement
}
}
}
Application principale
private void Form1_Load(object sender, System.EventArgs e) {
// instancie l'objet pour communiquer
comm= new Communication();
// instancie le délégué
comm.dataReceivedHandler= new
Communication.DataReceivedHandler(this.DonneeRecue);
}
// déclenché par le délégué lorsque une donnée est recue
private void DonneeRecue(string data) {
// affiche le message recu
Différences en Java et C# ■ 41
MessageBox.Show("donnée recue: "+data);
}
5.3 Les types valeur (ValueTypes et Structs)
En Java et C#, les données sur le tas (heap) sont libérées par le ramasse-miettes
lorsqu'elles ne sont plus utilisées, alors que les données situées sur la pile ne rentrent pas
sous la responsabilité du ramasse-miettes et sont instantanément supprimées au retour d'un
appel de méthode. De plus, les allocations sur la pile sont beaucoup plus rapides que celles
dans le tas. Enfin, les problèmes liés à la fragmentation de la mémoire n'existent pas dans la
pile.
C'est pourquoi, pour éviter d'utiliser inutilement le tas (heap), C# propose de stocker
directement certains objets dans la pile. Ces objets sont appelés types de valeur (ValueType).
D'ailleurs, en C#, l'ensemble des types primitifs sont des ValueType stockés sous la forme de
structures (cf. Int32). Contrairement aux classes, les ValueType sont toujours passées par
valeur et ne sont jamais libérées par le ramasse-miettes. De plus, les tableaux de ValueType
contiennent des valeurs et non des références d'objets, ce qui permet d'économiser du temps.
Tous les types struct héritent implicitement de la classe object. Une déclaration de
struct peut spécifier une liste d'interfaces implémentées, mais il n'est pas possible qu'elle
spécifie une classe de base. C'est pourquoi les structs ne sont jamais abstraits et sont
toujours implicitement finals (sealed). Comme l'héritage n'est pas géré dans les struct, la
visibilité par défaut déclarée est protected ou internal protected et les membres de
fonctions ne peuvent être abstract ou virtual.
Code C#
struct Point {
public int x;
public int y;
public Point( int x, int y) {
this.x = x;
this.y = y;
}
public override string ToString() {
return String.Format("({0}, {1})", x, y);
}
public static void Main(string[] args) {
Point start = new Point(5, 9);
Console.WriteLine("Start: " + start);
// La ligne suivante ne compilerait pas si Point était une classe.
Point end = new Point();
Console.WriteLine("End: " + end);
}
}
ENSICAEN - Spécialité Informatique
Pour déclarer un type de valeur, il suffit d'utiliser le mot-clé struct au lieu de class.
Pour instancier un type de valeur, le mot-clé new doit être utilisé tout comme une classe
normale. Les structures et les constructeurs se comportent différemment par rapport aux
classes. Dans le cas d'une classe, la création d'une instance doit toujours précéder l'utilisation
de l'objet. Si le mot clé new est omis, la création n'a pas lieu et la référence est nulle.
42 ■ Différences en Java et C#
5.4 L'identification de type (opérateur "as")
Cet opérateur ne possède pas d'équivalent en Java mais se rapproche du fonctionnement
du dynamic_cast de C++. Son fonctionnement consiste à exécuter une conversion explicite
d'une référence vers un type donné et en cas d'échec renvoie la valeur null. Cela permet en
une seule opération d'effectuer la conversion et l'assignation.
Code C#
MyClass mc = o as MyClass;
if(mc != null) { // test si la conversion est possible
mc.doStuff();
}
NOTE : L'opérateur as s'utilise aussi pour convertir des types de valeur.
5.5 Les propriétés
ENSICAEN - Spécialité Informatique
Les propriétés sont un moyen d'accéder aux propriétés membres d'une classe en
respectant les règles d'encapsulation. Dans le monde Java, les accesseurs et les mutateurs
sont utilisés à ces fins. L'avantage des propriétés en C# est de permettre à l'utilisateur
d'accéder aux attributs d'un objet de la même manière que s'il effectuait directement
l'opération object.attribut alors qu'en réalité il appelle une méthode de manière
totalement transparente.
Il est aussi possible de créer des propriétés en lecture seule (read-only), écriture seule ou
lecture/écriture si les accesseurs associés sont implémentés ou pas.
L'exemple ci-dessous illustre les différents types de propriétés :
Code C#
public class User {
public User(string name) {
this.name = name;
}
private string name;
// propriete en lecture/écriture pour l'attribut name
public string Name {
get {
return name;
}
}
private static int minimum_age = 13;
//read-write property for class member, minimum_age
public static int MinimumAge {
get {
return minimum_age;
}
set {
if(value > 0 && value < 100)
minimum_age = value;
else
Console.WriteLine("{0} is an invalid age, so minimum age remains at
{1}", value, minimum_age);
}
}
public static void Main(string[] args) {
User newuser = new User("Bob Hope");
Différences en Java et C# ■ 43
User.MinimumAge = -5; // affiche une erreur (valeur est invalide)
User.MinimumAge = 18;
// newuser.Name = "Kevin Nash"; cause une erreur de compilation
// puisque la propriété Name est en lecture seule
Console.WriteLine("Minimum Age: " + User.MinimumAge);
Console.WriteLine("Name: {0}", newuser.Name);
}
}
L'inconvénient des propriétés C# est qu'elles peuvent mener à des situations pour le
moins bizarres lorsqu'une exception se produit. Imaginez une contrainte sur une propriété
définissant le fait qu'une valeur ne doit pas dépasser un certain seuil. En cas de
débordement, une exception va être levée à l'intérieur de l'accesseur et le client devra traiter
cette erreur alors qu'il réalise simplement une affectation. C'est ce qui est représenté dans
l'exemple ci-dessous avec la classe Clock :
Code C#
Pour éviter ce genre de situation, il est préférable dans la mesure du possible de traiter
les exceptions dans le corps des accesseurs et si ce n'est pas possible, indiquez clairement
dans la documentation que cette méthode est susceptible de lever une exception que
l'utilisateur devra attraper.
5.6 Les Indexeurs
Les indexeurs ont une syntaxe spécifique permettant de surcharger l'opérateur [] d'une
classe. Ils sont utiles lorsqu'une classe est un conteneur pour d'autres types d'objets. Les
indexeurs supportent divers types comme index : entiers, chaînes de caractère, ... Il est aussi
possible de créer des indexeurs supportant les tableaux multi-dimensionels. Enfin, les
indexeurs peuvent être surchargés. En Java, les indexeurs sont implémentés à travers des
accesseurs. Ainsi, en Java on écrirait : myList.getElement(3) et en C# : myList[3]
Code C#
using System;
using System.Collections;
public class IndexerTest: IEnumerable, IEnumerator {
private Hashtable list;
public IndexerTest () {
index = -1;
list = new Hashtable();
}
// indexeur par le numéro
public object this[int column] {
get {
return list[column];
}
set {
list[column] = value;
ENSICAEN - Spécialité Informatique
try {
myClock.Hours
= 28; // exception puisque 28 est une heure invalide
myClock.Minutes = 15;
myClock.Seconds = 39;
} catch(InvalidTimeValueException itve) {
// détetermine quel attribut est invalide et reporte l'erreur
}
44 ■ Différences en Java et C#
}
}
// indexeur par le nom
public object this[string name] {
get {
return this[ConvertToInt(name)];
}
set {
this[ConvertToInt(name)] = value;
}
}
ENSICAEN - Spécialité Informatique
private int ConvertToInt(string value) {
string loVal = value.ToLower();
switch(loVal) {
case "zero": return 0;
case "one": return 1;
case "two": return 2;
case "three": return 3;
case "four": return 4;
case "five": return 5;
default:, return 0;
}
return 0;
}
public IEnumerator GetEnumerator(){ return (IEnumerator) this; }
private int index;
public bool MoveNext() {
index++;
if(index >= list.Count)
return false;
else
return true;
}
public void Reset() {
index = -1;
}
public object Current {
get {
return list[index];
}
}
public static void Main(string[] args) {
IndexerTest it = new IndexerTest();
it[0] = "A";
it[1] = "B";
it[2] = "C";
it[3] = "D";
it[4] = "E";
Console.WriteLine("Integer Indexing: it[0] = " + it[0]);
Console.WriteLine("String Indexing: it[\"Three\"] = " + it["Three"]);
Console.WriteLine("Printing entire contents of object via enumerating
through indexer :");
foreach( string str in it) {
Console.WriteLine(str);
}
}
}
Différences en Java et C# ■ 45
5.7 Les Directives de pré-processing
C# intègre un pré-processeur proposant un sous-ensemble limité des fonctionnalités
fournies par les pré-processeurs C/C++. Par exemple, la directive #include n'existe pas. Les
seules directives présentes sont liées aux opérations d'affectations d'identificateurs
permettant de réaliser de la compilation conditionnelle avec #define, #undef et #if,
#elif. Les directives #error et #warning provoquent l'affichage de messages d'erreurs ou
de warning pendant la phase de compilation. Enfin, #line permet de spécifier la ligne
correspondante au fichier source afin de reporter le numéro de ligne en cas d'erreur.
Code C#
#define DEBUG /* #define must be first token in file */
using System;
En Java, les directives de compilation peuvent être implémentées par les annotations. De
plus, l'utilisation des directives de compilation pour introduire des messages de débogage
est proscrite dans java. Il est lui préférée l'utilisation de test unitaire distinct du code
notamment en utilisant le framework JUnit.
5.8 Les Aliases
Le mot-clé using peut être utilisé pour assigner à un nom donné un type correspondant.
Le mécanisme est assez similaire aux typedef en C ou C++. Son comportement a été
détourné en C# pour permettre une meilleure lisibilité du code source, mais aussi pour
résoudre les problèmes de conflits entre les noms des namespaces.
Code C#
using Terminal = System.Console;
class Test {
public static void Main(string[] args) {
Terminal.WriteLine("Terminal.WriteLine is equivalent to
System.Console.Writeline");
}
}
5.9 La génération de code à l'exécution
Le package Reflection.Emit contient un ensemble de classes pouvant être utilisées
afin de générer des instructions .NET à l'exécution. Ces instructions sont ensuite compilées
en mémoire et peuvent être stockées physiquement sur disque sous la forme d'une assembly.
Ce mécanisme existe dans Java mais n'est pas proposé en standard dans les APIs. Les
moteurs de Servlets/JSP l'utilise pour générer le source d'une servlet et pour la charger en
mémoire. Les conteneurs EJB s'en servent pour générer l'implémentation des classes
ENSICAEN - Spécialité Informatique
class PreprocessorTest {
public static void Main(string[] args) {
#if DEBUG
Console.WriteLine("DEBUG Mode := On");
#else
Console.WriteLine("DEBUG Mode := Off");
#endif
}
}
46 ■ Différences en Java et C#
techniques (EjbObject) ou le code de persistance des Entity. Malheureusement, ces classes se
trouvent dans les packages sun.tools.* (tools.jar), ce qui limite fortement leur
utilisation.
5.10 Pointeur et code non protégé (unsafe)
Bien que l'ensemble du langage C# soit très proche de Java et que les pointeurs ne soient
pas gérés de manière intégré, leur utilisation n'est pas proscrite dans le cadre de blocs
"unsafe" ou non protégé. Il faut garder à l'esprit que tout code s'exécutant dans ce type de
bloc ne bénéficie pas de tous les services du Framework (vérification de types, ...). Cela
signifie que le programme doit faire une confiance aveugle dans ce genre de code. Leur
utilisation est appropriée lorsqu'il est nécessaire de faire appel à une zone de mémoire
spécifique, par exemple dans le cas de drivers utilisant des adresses d'entrées/sorties
particulières.
De plus, le garbage collector pouvant à tout moment ré-allouer des variables "managés",
il est absolument nécessaire de figer ces variables le temps du traitement par le bloc unsafe.
Cette opération est réalisée à l'aide du mot-clé "fixed".
Code C#
ENSICAEN - Spécialité Informatique
using System;
class UnsafeTest {
public static unsafe void Swap(int* a, int*b) {
int temp = *a;
*a = *b;
*b = temp;
}
public static unsafe void Sort(int* array, int size) {
for(int i= 0; i < size - 1; i++)
for(int j = i + 1; j < size; j++)
if(array[i] > array[j])
Swap(&array[i], &array[j]);
}
public static unsafe void Main(string[] args) {
int[] array = {9, 1, 3, 6, 11, 99, 37, 17, 0, 12};
Console.WriteLine("Unsorted Array:");
foreach(int x in array)
Console.Write(x + " ");
fixed( int* iptr = array ) { // must use fixed to get address of array
Sort(iptr, array.Length);
}//fixed
Console.WriteLine("\nSorted Array:");
foreach(int x in array)
Console.Write(x + " ");
}
}
5.11 Passage par référence
En Java, les arguments sont passés par valeur, cela signifie que lorsqu'une méthode est
appelée, les arguments sont copiés sur la pile (copie des valeurs primitives mais aussi des
références). En C#, comme en C++ et d'une certaine manière en C, il est possible de spécifier
qu'une liste d'arguments doit être passée par référence et non par valeur. Ce type de
caractéristique est particulièrement intéressante lorsqu'une méthode doit retourner une liste
d'objets modifiés. Ainsi, en Java il est impossible de swaper (intervertir) deux nombres
Différences en Java et C# ■ 47
entiers sans passer par leur type Wrapper.
En C#, les mot-clés pour spécifier qu'un paramètre doit être passé par valeur ou par
référence sont ref et out. Bien entendu, tout paramètre passé avec ref doit avoir été au
préalable initialisé, dans le cas où la méthode se charge de créer l'objet, le mot-clé out doit
être précisé.
Code Java
OUTPUT
a := 5, b := 10, s = Unchanged
Code C#
using System;
class PassByRefTest {
public static void ChangeMe(out string
s = "Changed";
}
public static void Swap(ref int x, ref
int z = x;
x = y;
y = z;
}
public static void Main(string[] args)
int a = 5, b = 10;
string s;
Swap(ref a, ref b);
ChangeMe(out s);
Console.WriteLine("a := " + a + ", b
}
}
OUTPUT
a := 10, b := 5, s = Changed
s) {
int y) {
{
:= " + b + ", s = " + s);
ENSICAEN - Spécialité Informatique
class PassByRefTest {
public static void changeMe(String s) {
s = "Changed";
}
public static void swap(int x, int y) {
int z = x;
x = y;
y = z;
}
public static void main(String[] args) {
int a = 5, b = 10;
String s = "Unchanged";
swap(a, b);
changeMe(s);
System.out.println("a := " + a + ", b := " + b + ", s = " + s);
}
}
48 ■ Différences en Java et C#
5.12 Les caractères spéciaux
C# fournit un moyen élégant pour résoudre le problème d'échappement des caractères
spéciaux dans une chaîne. Ainsi, les backslashes, tabulations, quotes et autres newlines
peuvent être intégrés à la chaîne sans avoir à utiliser une séquence d'échappement
particulière. La seule contrainte est de préfixer la déclaration de la chaîne avec le symbole @.
Code C#
ENSICAEN - Spécialité Informatique
using System;
class VerbatimTest {
public static void Main() {
//verbatim string
string filename = @"C:\My Documents\My Files\File.html";
Console.WriteLine("Filename 1: " + filename);
//regular string
string filename2 = "C:\\My Documents\\My Files\\File.html";
Console.WriteLine("Filename 2: " + filename2);
string snl_celebrity_jeopardy_skit = @"
Darrell Hammond (Sean Connery) : I'll take ""Swords"" for $400
Will Farrell
(Alex Trebek) : That's S-Words, Mr Connery.
";
Console.WriteLine(snl_celebrity_jeopardy_skit);
}
}
5.13 Détection de débordement
C# fournit une option pour explicitement détecter ou ignorer les débordements dans le
cas de conversion vers un type plus faible. Le débordement lève une exception du type
System.OverFlowException et comme la détection met en place des mécanismes pouvant
affecter les performances, il est possible de l'activer ou de la désactiver en spécifiant les motsclés checked et unchecked au début de blocs.
Code C#
using System;
class CheckedTest {
public static void Main() {
int num = 5000;
/* DEBORDEMENT I */
byte a = (byte) num; // débordement detecté si l'option /checked
// du compilateur est poisitionnée
/* DEBORDEMENT II */
checked {
byte b = (byte) num; /* débordement TOUJOURS détecté */
}
/* DEBORDEMENT III */
unchecked {
byte c = (byte) num; /* débordement JAMAIS détecté */
}
}
}
Différences en Java et C# ■ 49
5.14 L'implémentation explicite d'interface
Quelquefois, lorsqu'il est nécessaire d'implémenter une interface, il se peut que vous
soyez confronté à des problèmes de conflits de noms. Par exemple, imaginons la classe
FileRepresentation qui implémente les interfaces IWindow et IFileHandler. Les deux
interfaces possèdent une méthode Close() qui dans le cas de l'interface IWindow permet de
refermer une fenêtre et dans le cas de IFileHandler permet de refermer un fichier. En Java, il
n'existe aucune solution à ce genre de problème à part écrire deux méthodes possédant
chacune des noms différents. C# propose une alternative intéressante au développeur en lui
permettant d'implémenter explicitement les deux interfaces dans la classe
FileRepresentation. Ainsi, lorsque le client veut fermer un fichier, il spécifie l'interface
utilisée de la manière suivante : (IFileHandler) obj.Close() et ou (IWindow) obj.Close().
NOTE : Les méthodes de ces interfaces sont privées et ne peuvent être appelées sans
passer par une conversion explicite
Code C#
using System;
ENSICAEN - Spécialité Informatique
interface Ivehicle {
//identifie le vehicle par le modele, la marque, et l'année
void IdentifySelf();
}
interface Irobot {
//identifie le robot par son nom
void IdentifySelf();
}
class TransformingRobot : IRobot, Ivehicle {
string model;
string make;
short year;
string name;
TransformingRobot(String name, String model, String make, short year) {
this.name = name;
this.model = model;
this.make = make;
this.year = year;
}
void Irobot.IdentifySelf() {
Console.WriteLine("My name is " + this.name);
}
void Ivehicle.IdentifySelf() {
Console.WriteLine("Model:" + this.model + " Make:" + this.make + "
Year:" + this.year);
}
public static void Main() {
TransformingRobot tr = new TransformingRobot("SedanBot", "Toyota",
"Corolla", 2001);
// tr.IdentifySelf(); ERREUR
IVehicle v = (IVehicle) tr;
IRobot r
= (IRobot) tr;
v.IdentifySelf();
r.IdentifySelf();
}
}
50 ■ Différences en Java et C#
OUTPUT
Model:Toyota Make:Corolla Year:2001
My name is SedanBot
6. Concepts avancés
6.1 Portabilité multi-plateforme (Write Once, Run Anywhere)
Une notion importante qui a contribué à la réussite du langage Java est sa capacité à
s'exécuter sur n'importe quelle plate-forme sans avoir à ré-écrire le code. Sun officiellement
supporte Linux, Windows, Solaris et d'autres éditeurs tiers assurent le portage de la machine
virtuelle sur OS/2, AIX et MacOS.
À l'heure actuelle, la plate-forme .NET n'est vraiment utilisable que sur les systèmes
Windows. Plusieurs efforts de portage ont été entrepris à travers le monde. Citons dans ce
domaine, les projets Mono (http:///www.go-mono.org), Halcyon (conversion .NET vers
Java) ou encore Microsoft avec le portage de .NET sur FreeBSD (nom de code : rotor).
6.2 Les extensions
ENSICAEN - Spécialité Informatique
Les extensions Java sont un moyen d'enrichir les APIs internes du JDK dans le but de
fournir une implémentation spécifique de certains paquets système. Cela signifie que
l'extension est intégrée au sein des classes java.lang, java.util, java.net, etc, et
peut-être utilisée par l'ensemble des applications utilisant Java.
Pour faire un parallèle avec C#, c'est comme si l'extension proposée s'intégrait dans le
namespace System contenu dans l'assembly System.dll.
6.3 Le chargement dynamique de classe
Une des fonctions les plus intéressantes de Java est la possibilité de pouvoir redéfinir le
comportement du chargeur de classes associé à une procédure d'invocation distante. Le
chargement dynamique de classe permet en Java de télécharger une classe à partir de
n'importe quelle source (HTTP, Fichier, FTP, JMS, ...) et d'introduire ainsi de nouveaux types
dans la machine cible. Il est donc possible d'étendre à loisir les fonctionnalités d'un système
sans avoir à redéployer l'application toute entière.
Code Java
public class MyRMIServer extends UnicastRemoteObject implements
SomeInterface {
public MyRMIServer() throws RemoteException{ super();}
public String obtainName(IStockTicker ticker){
String stock_ticker = ticker.getTicker();
if (stock_ticker.equalsIgnoreCase("MSFT"))
return "Microsoft Corporation";
else if(stock_ticker.equalsIgnoreCase("SUNW"))
return "Sun Microsystems";
else
return "Unknown Stock Ticker";
}
}
La méthode distante obtainName() dans la classe ci-dessus accepte des types qui
Différences en Java et C# ■ 51
implémentent l'interface StockTicker. Le client doit juste se contenter de passer un objet du
type de l'interface attendue. Par exemple, le type CAC40Stock qui n'existe pas sur le serveur
peut-être téléchargé à partir d'une source quelconque et s'utilise comme s'il avait toujours
existé sur le serveur, et cela sans arrêter ou relancer l'application.
En C#, l'API .NET Remoting fournit un mécanisme similaire permettant de télécharger à
distance du code à partir de n'importe quelle machine à partir du moment où le client publie
l'URL de l'assembly.
6.4 Les interfaces contenant des champs
En Java, il est possible de déclarer des constantes dans les interfaces qui seront
disponibles aux classes dérivées. Cette technique est souvent utilisée dans Java comme
alternative aux énumérations notamment dans des interfaces utilisées entre équipes de
développement différentes et ainsi regrouper toutes les informations dans l'interface.
En C#, cette caractéristique n'existe pas et ne constitue pas réellement un problème dans
la mesure où ce langage fournit déjà un mécanisme d'énumération (enums)
6.5 Les classes anonymes (Inner classes )
L'exemple qui suit illustre l'implémentation du patron de conception État (automate à
états finis) en utilisant des classes anonymes
Code Java
// Une instance of this class represents the current state of a
ClientView GUI.
public abstract class ClientState {
// This instance of the class is used to signify that the user is not
// logged in. The only thing a user can do in this state is login and
exit.
public static ClientState NOT_LOGGED_IN = new ClientState() {
public void setMenuState(ClientView cv) {
cv.setMenuEnabledState(false);
cv.menuLogin.setEnabled(true);
cv.menuExit.setEnabled(true);
//can't type
cv.textArea.setEnabled(false);
}
public String toString(){
return "ClientState: NOT_LOGGED_IN";
}
};
// This instance is used to signify that the user is logged in
// but has not yet created a document to work with.
// The user cannot type or save anything in this mode.
public static ClientState NO_OPEN_DOCUMENT = new ClientState() {
public void setMenuState(ClientView cv) {
cv.setMenuEnabledState(false); /* disable all menus */
cv.menuLogin.setEnabled(true);
ENSICAEN - Spécialité Informatique
Une classe anonyme est une classe instanciée à l'initialisation du type et ne possédant
pas de nom. Les classes anonymes sont souvent utilisées lorsqu'il est nécessaire d'avoir une
et une seule instance d'un type donné existant dans l'application. En Java, leur utilisation est
souvent cantonnée à la gestion des événements, les classes callback sont la plupart du temps
anonyme.
52 ■ Différences en Java et C#
cv.menuExit.setEnabled(true);
cv.menuOpenFile.setEnabled(true);
cv.menuNewFile.setEnabled(true);
//can't type
cv.textArea.setEnabled(false);
}
public String toString(){
return "ClientState: NO_OPEN_DOCUMENT";
}
};
// This instance is used to signify that the user is editing a file.
// In this mode the user can use any functionality he/she sees fit.
public static ClientState EDITTING_DOCUMENT = new ClientState() {
public void setMenuState(ClientView cv) {
cv.setMenuEnabledState(true);
cv.textArea.setEnabled(true);
}
public String toString() {
return "ClientState:EDITTING_DOCUMENT";
}
};
ENSICAEN - Spécialité Informatique
// Default constructor private to stop people from
// directly creating instances of the class.
private ClientState() {;}
// This disables various elements of the ClientView's menu dependent on
// which ClientState object this is.
public abstract void setMenuState(ClientView cv);
}
Voici un exemple de code utilisant la classe ClientState définie ci-dessus :
bool loginUser(String username, String passwd) {
//check if already logged in
if (myGUI.state == ClientState.NOT_LOGGED_IN)
return true;
//enable parts of the GUI if the user authenticates
if (userAuthenticated(username, passwd)){
myGUI.state = ClientState.NO_OPEN_DOCUMENT;
myGUI.state.setMenuState(myView);
return true;
}
return false;
}
6.6 La gestion de version des Assemblies
La version des Assemblies est une des fonctionnalités les plus intéressantes de C# et
de .NET en général. Ainsi, vous avez la possibilité d'exécuter dans un même domaine
d'application plusieurs versions différentes d'une même classe. Cela est possible du fait que
les programmes C# référencent statiquement des bibliothèques de classes à l'aide de leur
nom mais aussi de leur version. Vous ne risquez donc pas d'avoir des problèmes de
cohérence entre différentes versions de DLL s'exécutant sur la même machine. Les
développeurs Java connaissent bien les affres du déploiement d'applications Web (JSP et
servlets) référençant de multiples versions de parseur XML (xerces) ou de bibliothèque
ant.jar. Cela est dû au fait que les ClassLoaders Java fonctionnent sur le principe de premier
Différences en Java et C# ■ 53
chargé premier servi. Même si le modèle Java du chargement dynamique de classes est, il
faut l'avouer plus riche que celui de C#, il n'en demeure pas moins que cette fonctionnalité
manque cruellement dans le JDK.
7.
Conclusion
Microsoft déclare de façon explicite que le langage C# est directement basé sur le langage
Java dont il a repris la plupart des choses en apportant de nouvelles solutions aux choses
jugées manquantes à l'époque. Entre-temps, Java a développé aussi des solutions pour ces
lacunes. Aujourd'hui peu de choses diffèrent entre ces deux langages comme nous l'avons
montré précédemment.
Java est aujourd'hui le langage plus utilisé dans le monde de la programmation logiciel.
Java est un langage portable sur tous les types de systèmes d'exploitation, alors que C# reste
très dépendant de Windows, même si le projet Open-Source Mono a pour objectif de porter
C# sur Linux et MacOS mais les performances restent très médiocres. De plus, la machine
virtuelle « HotSpot » développée par Sun est la machine virtuelle la plus puissante sur le
marché et avec une version 7 qui accroît encore ses performances.
Le langage C# pour sa part a repris d'autres langages des concepts intéressants
notamment autour de :
► Un mécanisme intégré permettant d'étendre les fonctionnalités de base d'une classe au
niveau des sources (cf Attributs de runtime).
De plus, le langage C# bénéficie de la puissance de l'éditeur Microsoft qui en font
certainement un langage qui va compter dans le monde du développement logiciel.
La plupart des développeurs, spécialement ceux possédant une culture C ou C++
apprécieront à leur juste valeur les caractéristiques de surcharge d'opérateurs, pointeurs,
délégués et autre libération de mémoire déterministe faisant de C# un langage véritablement
riche. Aussi, les développeurs Java désirant se former à ce nouveau langage seront
agréablement surpris par le fait que certaines caractéristiques manquantes à Java sont
supportées par C#, à savoir les types de valeurs, le mot-clé const ou les délégués. D'un autre
côté, le manque de support d'exception typées, l'absence de classes anonymes, la portabilité
multi-plateforme ou le fait que la classe ne soit pas la plus petite unité de déploiement (c'est
l'assembly) font que le choix entre C# et Java n'est plus aussi clair.
ENSICAEN - Spécialité Informatique
► Un mécanisme de gestion de version de classes avec la possibilité d'avoir plusieurs
versions différentes de classes chargées en même temps en mémoire.
54 ■ Différences en Java et C#
Révision
Version
0.1
0.2
Date
Commentaire
Basé sur l'article de Dare Obasanjo (traduction de Alain Vizzini
23 dec, 2012
http://alain.vizzini.free.fr/)
3 mar, 2014 Révision.
ENSICAEN - Spécialité Informatique