
FAQ JavaConsultez toutes les FAQ
Nombre d'auteurs : 53, nombre de questions : 231, dernière mise à jour : 7 juin 2009
Sommaire→Concepts fondamentaux→Les notions- Qu'est-ce que l'héritage ?
- Qu'est ce qu'une classe abstraite ?
- Qu'est-ce qu'une classe interne ?
- Quels sont les différents types de classes internes (nested classes) ?
- Qu'est ce qu'une interface ?
- Qu'est ce qu'une méthode 'deprecated' ?
- Qu'est-ce que la sérialisation ?
- Quelles sont les règles à respecter pour redéfinir/implémenter une méthode ?
- C'est quoi la surcharge (ou encore overload)des méthodes ?
- Comment cloner un objet ?
- [Java 5.0] Qu'est-ce que les Generics (types paramétrés) ?
- [Java 5.0] Qu'est-ce que l'auto-boxing / auto-unboxing ?
- [Java 5.0] Qu'est-ce qu'une annotation ?
- [Java 5.0] Qu'est-ce qu'une enum (type énuméré) ?
- Qu'est-ce qu'une constante ?
- Qu'est-ce qu'un membre 'synthetic' ?
- [Java 5.0] Qu'est-ce qu'une méthode 'bridge' ?
L'héritage est un des principaux concepts de la programmation orientée objet. L'héritage permet de définir une relation de "filiation" entre classes. Ainsi une classe fille (ou sous-classe) étend une classe mère (ou super-classe). L'héritage permet en général de spécialiser une classe.
Pour indiquer qu'une classe hérite d'une autre, il faut utiliser le mot-clé extends :
public class Fille extends Mere{
//ici le code spécifique de la classe fille
}
L'héritage implique plusieurs choses :
La fille hérite du type de la mère :
public class Couleur(){
String nom;
public Couleur(String nom){
this.nom = nom;
}
}
public class Rouge extends Couleur{
public Rouge(){
super("rouge");
}
}
public class AutreClasse(){
public void setCouleur(Couleur uneCouleur){
//etc.
}
public static void main(String[] args){
AutreClasse ac = new AutreClasse();
ac.setCouleur(new Couleur("vert"));
ac.setCouleur(new Rouge());
}
}
La fille hérite de plusieurs attributs, méthodes et constructeurs de la mère
L'accès à ces attributs ou méthodes se fait avec le mot clef super. Pour plus d'informations, voir Que signifient les mots-clés this et super ?
Voici comment est définie l'accessibilité aux composantes de la super-classe, en fonction des modificateurs :
| Mot-clé | Accès |
|---|---|
| public | Oui |
| "rien" | Oui, seulement si la classe fille se trouve dans le même package que la super-classe. |
| protected | Oui, quel que soit le package de définition de la classe fille. |
| private | Non. |
Regardez les liens ci dessous qui pourront vous éclairer un peu plus sur cette notion.
Lien : Que signifient les mots-clés this et super ?
Lien : Comment faire pour hériter de plusieurs classes ?
Lien : Qu'est ce qu'une interface ?
/** La classe abstraite employée : */
public abstract class Employe {
// bla bla bla
/** définition d'une méthode abstraite
* on notera qu'il n'y a pas d'instruction et un point-virgule à la fin
*/
public abstract void licencier();
}
// Class Ouvrier
public class Ouvrier extends Employe {
// définition du code de licencier
public void licencier() {
// on definit le code
System.out.println("Dehors !");
}
}
// Class Patron
public class Patron extends Employe {
// définition du code de licencier
public void licencier() {
System.out.println("Veuillez comprendre que dans la conjoncture actuelle ... !");
}
}
Toutes les personnes trouvant ce code ridicule sont priées de joindre un autre exemple avec leur mail de protestation :-)
Lien : Qu'est ce qu'une interface ?
Une classe interne est une classe déclarée à l'intérieur d'une autre classe. Elle possède les mêmes possibilités qu'une autre classe, mais elle est toujours dépendante de sa classe conteneur et ne peut être utilisé que par la classe conteneur.
Exemple :
class Outer{
class Inner {
}
}
Une classe interne peut accéder de manière transparente aux membres de l'instance de la classe dont elle fait partie. Comme par exemple dans ce code :
class Outer{
int i = 100;
class Inner {
int k = i ;
}
}
Une classe interne ne peut par contre pas comporter de contexte static.
On peut aussi déclarer une classe interne comme static, ce qui fait qu'elle ne sera plus liée à l'instance de la classe conteneur et qu'elle pourra déclarer des contextes statiques. Mais elle ne pourra plus utiliser les variables d'instance de la classe conteneur, mais seulement les variables de classe (statiques).
Une classe interne est une classe qui est déclarée à l'intérieur d'une autre classe. Le principal avantage des classes internes vient du fait qu'elles ont accès à tous les membres de leur classe conteneur quel que soit le niveau de visibilité. Ainsi les membres private de la classe conteneur sont visibles par toutes ses classes internes.
Note : En réalité le compilateur génèrera implicitement une méthode d'accès synthétique de visibilitée package-only.
On distingue toutefois quatre grands types de classes internes :
- Les classes internes static ("static nested classes"), qui correspondent à de simples classes déclarées à l'intérieur d'une autre classe.
- Les classes internes ("inner classes"), qui conserve un lien fort avec une instance de la classe conteneur.
- Les classes locales, déclarée dans une méthode, et qui ne sont valide qu'à l'intérieur de ce bloc.
- Les classes anonymes, déclaré en ligne dans le code.
Les deux premiers types ("static nested classes" et "inner classes") étant déclarées au même niveau que les membres de la classes, ils peuvent donc bénéficier des mêmes possibilitées de visibilités : public, protected, package-only("rien") ou private. Une classe private ne peut être utilisée que par la classe conteneur tandis qu'une classe protected peut également être utilisé par une classe fille ou une classe du même package.
Note : Les classes standards ne peuvent pas être déclarées protected ou private, puisque cela n'aurait aucun sens (une classe ne peut hériter d'une autre classe que si cette dernière lui est visible).
Les classes internes static ("static nested classes") correspondent tout simplement à des classes standards déclarées à l'intérieur d'une autre classes. Elles se distinguent par la présence du mot-clef static dans leurs définitions.
Par exemple :
public class TopLevelClass {
private String privateField;
public static class StaticNestedClassComparator implements Comparator<TopLevelClass> {
public int compare(TopLevelClass o1, TopLevelClass o2) {
// On accède directement aux champs privée :
return o1.privateField.compareTo(o2.privateField);
}
}
}
Ces méthodes peuvent être instancier directement en les préfixant du nom de la classe conteneur (à condition qu'elles soit visible bien entendu) :
TopLevelClass.StaticNestedClassComparator instance = new TopLevelClass.StaticNestedClassComparator();
Les classes internes ("inner classes") ne sont pas déclarées en static, et elles gagnent par la même occasion un lien étroit avec une instance de la classe conteneur. En effet les classes internes ne peuvent être instancié qu'à partir une instance de la classe parente, avec laquelle elle gardera une relation pendant toute son existence.
Il est ainsi possible d'accéder à l'instance courante via le mot-clef "NomDeLaClasseConteneur.this". Par exemple :
public class TopLevelClass {
private String privateField;
public class InnerClassRunnable implements Runnable {
public void run() {
// On peut accéder directement aux champs privées de l'instance lié :
System.out.println(TopLevelClass.this.privateField);
}
}
public InnerClassRunnable create() {
// On crée une instance de l'inner-class qui sera lié avec l'instance courante (this)
return new InnerClassRunnable();
}
}
En contrepartie, il est ainsi obligatoire d'instancier ce type de classe depuis une des méthodes d'instances de la classe conteneur (par exemple create() ici), ou en utilisant une référence de l'instance de la classe parente :
TopLevelClass topLevelInstance = new TopLevelClass();
TopLevelClass.InnerClassRunnable innerInstance = topLevelInstance.new InnerClassRunnable();
Mais ce type d'écriture est généralement déconseillé.
Note : Pour réaliser ce lien entre la classe interne et la classe conteneur, le compilateur rajoutera automatiquement un paramètre du type de la classe conteneur à chacun des constructeurs de la classe interne, ainsi qu'un attribut d'instance qui conservera cette valeur. Le code généré est donc sensiblement identique au code suivant :
public class InnerClassRunnable implements Runnable {
final TopLevelClass topLevelClass;
public InnerClassRunnable(TopLevelClass topLevelClass) {
this.topLevelClass = topLevelClass;
}
public void run() {
// On peut accéder directement aux champs privées de l'instance lié :
System.out.println(topLevelClass.privateField);
}
}
Note : Les interfaces, annotations ou enums déclarées à l'interieur d'une classe ne peuvent pas être liées avec une instance de la classe parente, et sont donc implicitement déclarées static.
Les classes locales sont des classes déclarées au sein même d'une méthode ou d'un bloc de code. Elles ne peuvent donc être utilisées qu'à l'intérieur de ce même bloc et seront complètement inexistantes de l'extérieur.
public static void main(final String[] args) throws Exception {
// Déclaration de la classe au sein d'une méthode :
class LocalClass {
public void sayHello() {
System.out.println("Hello World");
}
}
LocalClass c1 = new LocalClass();
LocalClass c2 = new LocalClass();
c1.sayHello();
c2.sayHello();
}
Les classes anonymes correspondent à une variante des classes locales, dans le sens où elles sont aussi déclarées à l'intérieur d'un bloc de code. Elles permettent de définir une classe à "usage unique" en implémentant une seule interface ou en héritant d'une classe directement, par exemple :
public static void main(final String[] args) throws Exception {
// Implémentation d'une interface :
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello World");
}
};
// Héritage d'une classe :
Thread thread = new Thread() {
public void run() {
System.out.println("Hello World");
}
};
thread.start();
new Thread(task).start();
}
Note : Lorsqu'elles sont utilisées dans une méthode d'instance, les classes locales et les classes anonymes peuvent être lié à l'instance courante (this) de la même manière que les classes internes non-static.
Note : Il faut noter également que mis à part pour les "classes internes static", les classes internes n'acceptent pas d'attribut static, mis à part s'il s'agit de constantes.
Lien : Qu'est-ce qu'une constante ?
Lien : Qu'est-ce qu'un membre 'synthetic' ?
Lien : Que signifient les mots-clés public, private et protected ?
Runnable = capable d'être exécuté
Drawable = capable de s'afficher
La notion d'interface permet ainsi
-> de découper de manière à la fois élégante et très puissante l'aptitude (contrat) de l'implémentation.
Par exemple l'interface Enumeration permet de parcourir une "liste" d'objets d'un bout à l'autre sans se préoccuper de l'implémentation sous-jacente (un tableau, une hashtable, Collection, etc.). En effet il suffit de faire un
while (Enumeration e = ...; e.hasNextElement(); ) {
MonObjet o = (MonObjet)e.next();
// Faire qqc avec o
}
-> De bien séparer les activités, et d'améliorer ainsi la lecture du code. En effet, la seule lecture de la déclaration de la classe nous permet de prendre connaissance de l'intégralité des activités de celle-ci. Par exemple une classe, disons "Ensemble" qui implémente l'interface Sortable nous renseigne dès sa déclaration sur la notion d'aptitude au tri qui lui a été attribuée.
Pour mieux comprendre la notion de séparation des activités, il peut être intéressant de lire les documentations concernant jakarta-avalon.
- Le plus généralement il s'agit d'un renommage de la méthode. Certaines méthodes sont héritées des toutes premières versions de JAVA, les conventions de nommage n'étant pas toujours respectées, certaines méthodes ont été renommées par la suite... (Les exemples sont très fréquents dans SWING)
- Quelques méthodes sont devenues obsolètes suite à une évolution de l'architecture de l'Api. (La gestion des dates par exemple)
- Enfin, certaines méthodes se sont révélées dangereuses et ne doivent plus être utilisées. Les méthodes de la classe java.lang.Thread peuvent conduire a un état incohérant des objets ou un arrêt de l'application (plus d'info)
public class MaClass {
/** Une méthode dépréciée
* @deprecated
*/
public void maMethodeDepreciee() {
}
}
Lien : Pourquoi toutes les méthodes de la classe Thread sont marquées 'deprecated' ?
Java, permet de sauvegarder l'état d'un objet à un instant donné dans un flux d'octets. On dirigera généralement ce flux dans un fichier pour effectuer une sauvegarde. Le principal avantage de la sérialisation, c'est d'être complètement intégré à l'api Java et donc de ne nécessiter presque aucun code supplémentaire.
public class Writeable implements java.io.Serializable
Note : toutes les variables de la classe doivent également être des objets sérialisables (ou des types primitifs).
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("monFichier.sav"));
out.writeObject(monObject1);
out.writeObject(monObject2);
out.close();
} catch( IOException e ) {
}
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("monFichier.sav"));
MonObject1 monObject1 = (MonObject1)in.readObject();
MonObject2 monObject2 = (MonObject2)in.readObject();
in.close();
} catch( ClassNotFoundException e1 ) {
} catch( IOException e2 ) {
}
Note : Il est possible de ne pas sauvegarder certains attributs de la classe en utilisant le mot clé "transient". Plus d'info ici.
Lorsqu'on redéfinit une méthode d'une classe parente, ou de lors de l'implémentation
d'une méthode d'une interface, on doit obligatoirement conserver la signature
exacte de la méthode d'origine : c'est le contrat que l'on doit respecter.
Toutefois, il n'est pas figé et il est donc possible de modifier certains
éléments. Pour cela, nous allons prendre la déclaration de la méthode suivante en exemple :
protected Number getValue(int value) throws IOException;
Il est donc possible de modifier les éléments suivants :
1) La portée de la méthode :
Il est en effet possible de changer la portée de la méthode, à condition
de l'élargir. Ainsi une méthode protected peut devenir public
alors qu'une méthode sans modificateur (visibilité limitée au package) pourra devenir protected ou
public...
Cela est possible car le contrat de la méthode original est respecté (on se contente
d'étendre l'accès à la méthode). Par contre l'inverse reste interdit ! (Impossible de
passer une méthode public en protected ou private par exemple).
Ainsi, la méthode ci-dessous est tout à fait valable :
public Number getValue(int index) throws IOException;
2) Les Exceptions retournées :
Il est possible de modifier la déclaration des exceptions renvoyées par
la méthode, tant que l'on respecte celle de la méthode parente. Il est
donc possible de :
- Supprimer l'exception : en effet, en ne renvoyant pas d'exception, on respecte le contrat original car le throw signifie "la méthode peut renvoyer une exception", mais ce n'est pas une obligation.
- Spécialiser le type de l'exception : en indiquant par exemple une exception qui hérite de celle définit dans la signature de la méthode parente.
Ainsi, les deux méthodes suivantes sont valables (puisque FileNotFoundException et
ZipException héritent de IOException) :
protected Number getValue(int value);
protected Number getValue(int value) throws FileNotFoundException, ZipException;
3) La covariance du type de retour (Java 5.0) :
La version 5.0 de Java apporte une nouvelle possibilité : on peut
désormais modifier le type de retour de la méthode. Toutefois, il faut
que le nouveau type hérite du type de retour d'origine afin de ne pas
rompre le contrat. Ainsi, notre méthode pourrait retourner un
Long (puisque Long hérite de Number) :
protected Long getValue(int value) throws IOException;
Au final, on peut obtenir des méthodes très différentes alors qu'elles
redéfinnissent bien la même méthode :
public Double getValue(int value);
protected Long getValue(int value) throws FileNotFoundException, ZipException;
etc...
Attention, les changements de signature affecteront bien sûr les classes
filles...
De plus, il peut devenir difficile de voir qu'une méthode en redéfini
une autre avec tant de modifications. Dans ce cas il est fortement conseillé
d'utiliser l'annotation @Override de Java 5.0 (si possible)...
Lien : Que signifient les mots-clés public, private et protected ?
Lien : La covariance dans le JDK1.5 de Fabrice Sznajderman
C'est le fait de déclarer plusieurs méthodes avec le même nom mais avec des paramètres et/ou type de retour différents.
Une méthode qui surcharge une autre doit obligatoirement :
- avoir le même nom de la méthode surchargée.
- être déclarée dans la même classe ou dans une classe fille.
- avoir une signature différente : paramètres différents en nombre, en types ou en ordre de ceux de la méthode surchargée.
public class Salutation {
public void disBonjour(){
System.out.println("Bonjour inconnu !");
}
public void disBonjour(String nom){
System.out.println("Bonjour "+nom+" !");
}
}
Dans cet exemple, la méthode disBonjour est surchargée dans le sens où elle peut être appelée de deux façons différentes :
Salutation salutation = new Salutation();
salutation.disBonjour();
salutation.disBonjour("Développeur");
Sortie :
Bonjour inconnu !
Bonjour Développeur !
Pour cloner un objet, il suffit d'appeler sa méthode clone(). Cet objet doit obligatoirement implémenter l'interface Cloneable.
S'il n'implémente pas cette interface, il n'est pas possible de compiler le code en raison de la visibilité de la méthode clone() définie dans la classe Object.
Attention:
Le clonage d'un objet n'opère que sur un niveau et non en profondeur.
Ainsi lors du clonage d'un Vector par exemple, seul le Vector est cloné; les objets contenus dans le Vector ne le sont pas, simplement il est désormais possible d'ajouter ou d'enlever des éléments d'un des Vector sans influencer l'autre.
Pour effectuer un "deep" clone, c'est-à-dire cloner aussi les objets, il faut écrire une boucle qui clonera un à un les objets contenus dans le Vector originel.
Les Generics permettent de s'abstraire du type réel des objets lors de la conception d'une classe ou d'une méthode tout en conservant un code sécurisé. En effet, contrairement au polymorphisme tel qu'il était utilisé jusque là dans Java, les Generics permettent de définir le type réel des objets lors de leurs utilisations, et permet ainsi de s'affranchir des multiples cast peu pratiques et dangereux en cas de mauvaise utilisation. Ainsi, les Generics augmentent la sécurité en reportant à la compilation des erreurs qui survenait à l'exécution...
Prenons pour exemple l'utilisation des collections de Java, avec une méthode qui permet de calculer la moyenne d'une List de Float :
public float moyenne (List listNote) {
float moyenne = 0.f;
Iterator iterator = listNote.iterator();
while (iterator.hasNext()) {
Float note = (Float) iterator.next();
moyenne += note.floatValue();
}
return moyenne/listNote.size();
}
Cette méthode toute simple peut poser problème si on l'utilise mal. En effet, si un seul des éléments de la liste passée en paramètre n'est pas du type Float, on se retrouve avec une ClassCastException. Si ce type de problème est facilement décelable dans de petit programme, il en est tout autrement dans de gros projet, et en particulier si la liste en question peut être remplie par différents modules...
En permettant de typer des objets, les generics reportent ces problèmes à la compilation. Ainsi la méthode devient :
public float moyenne (List<Float> listNote) {
float moyenne = 0.f;
Iterator<Float> iterator = listNote.iterator();
while (iterator.hasNext()) {
Float note = iterator.next();
moyenne += note.floatValue();
}
return moyenne/listNote.size();
}
Le cast a disparu car il a été remplacé par le typage de la liste et de son itérateur grâce au <Float> après le nom du type, qui permet d'indiquer que la List et l'Iterator sont tous les deux paramétrés avec des Float. Ainsi la méthode moyenne() ne risque plus de provoquer des ClassCastException puisque tous les éléments de la liste sont forcément des Float...
Enfin, il est à noter que l'utilisation cumulée des Generics, de l'auto-boxing et de la boucle for étendu simplifie grandement la méthode :
public float moyenne (List<Float> listNote) {
float moyenne = 0.f;
for (float note : listNote) {
moyenne += note;
}
return moyenne/listNote.size();
}
Les Generics apportent de grands changements qu'il serait difficile de décrire dans une simple réponse... La présentation de Tiger de Lionel Roux (lien ci-dessous) présente le concept plus en détail.
L'auto-boxing gère la transformation des types primitifs (boolean, byte, char, short, int, long, float, double) vers la classe 'wrapper' correspondante (Boolean, Byte, Character, Short, Integer, Long, Float, Double) et inversement pour l'auto-unboxing...
Tout est transparent, il n'y a donc plus aucune conversion explicite. Ainsi, le code suivant est tout à fait correct :
Integer integer = 0;// Integer integer = new Integer(0);
int j = integer;// int j = integer.intValue();
Map map = new HashMap();
map.put ( 2.1, "Valeur" );// map.put (new Double(2.1), "Valeur");
List list = new ArrayList();
list.add (true);// list.add (new Boolean(true));
etc...
Lien : Présentation de Tiger : L'autoboxing des types primitifs
Les annotations permettent de poser des 'marqueurs' sur divers éléments du langage. Elles peuvent ensuite être utilisées par le compilateur ou d'autre outils de gestions des sources afin d'automatiser certaines tâches, voir directement pendant l'exécution de l'application...
Dans le code source, les annotations se distinguent par la présence d'un arobase (@) devant leurs noms (à l'instar des tags Javadoc), et elle se déclare avec le mot-clef @interface :
public @interface SimpleAnnotation {
}
public @interface AttributAnnotation {
String value();
int count();
}
Elle s'utilise en plaçant l'annotation devant l'élément à annoter, avec les valeurs des éventuels attributs entre parenthèses, par exemple :
@SimpleAnnotation
public class MaClasse {
@SimpleAnnotation
protected String name;
@SimpleAnnotation protected int value;
@AttributAnnotation(value="info", count=3);
public MaClasse () {
}
@SimpleAnnotation
@AttributAnnotation(value="m", count=1);
public void method () {
}
Java 5.0 a introduit trois annotations de base :
- @Deprecated qui permet d'indiquer qu'un élément est déprécié et ne devrait plus être utilisé.
- @Override devant une méthode permet d'indiquer qu'elle surcharge une méthode héritée de la classe parent.
- @SuppressWarnings permet d'ignorer certains warnings lors de la compilation (*).
(*) L'annotation @SuppressWarnings n'est gérée par le compilateur standard du JDK 5.0 qu'à partir de l'update 6 (Voir Bug 2125378).
Il existe également des méta-annotations conçues exclusivement pour être utilisées sur d'autres annotations :
- @Documented permet d'indiquer que l'annotation doit figurer dans le documentation générée par javadoc.
- @Inherited indique que l'annotation doit être héritée par les classes filles.
- @Retention spécifie de quelle manière l'annotation doit être conservée par le compilateur et la JVM.
- @Target permet de limiter les éléments du langage qui peuvent prendre cette annotation.
De plus, Java 5.0 a introduit un nouvel outil en ligne de commande permettant d'analyser le code à la recherche des annotations avant la compilation : APT (Annotation Processing Tool).
Lien : Tutoriel : Les Annotations de Java 5.0
Lien : Guide : Annotation Processing Tool
Définition :
Une enum représente un type énuméré, c'est à dire un type
qui n'accepte qu'un ensemble fini d'éléments. Introduit avec Java 5.0,
ce nouveau type permet donc de créer simplement des énumérations...
Utilisation / but :
Dans sa forme la plus basique, une enum contient simplement la
liste des valeurs possibles qu'elle peut prendre. Elle se déclare comme
une classe mis à part que l'on utilise le nouveau mot-clef enum,
par exemple :
public enum Season {
spring,summer, automn, winter;
}
Toutefois, une énumération reste une classe Java, elle accepte donc des champs,des méthodes et des constructeurs. Par exemple, on pourrait compléter l'énumération avec le nom français de la saison :
public enum Season {
spring("Printemps"),summer("Eté"), automn("Automne"), winter("Hiver");
protected String label;
/** Constructeur */
Season(String pLabel) {
this.label = pLabel;
}
public String getLabel() {
return this.label;
}
}
Les constructeurs des enum n'acceptent pas de modificateurs d'accessibilité (public, protected, etc.) car ils ne sont utilisés que pour initialiser les différentes valeurs de l'énumération. Le code suivant est ainsi incorrect :
Season s = new Season ("Hiver");
De plus, chaque enum possède deux méthodes statiques implicites permettant d'accéder aux différentes valeurs :
- Season.values() retournera un tableau de Season avec toutes les valeurs possibles.
- Season.valueOf(String) retournera la Season dont le nom est passé en paramètre (par exemple, Season.valueOf("spring") retournera Season.spring).
Les différentes valeurs de l'enum correspondent à des constantes
statiques et publiques et peuvent donc être accédées directement comme les
champs public static (par exemple avec Season.winter).
Enfin, les enums peuvent directement être utilisées dans un switch :
public void method (Season season) {
switch (season) {
case spring:
System.out.println("Les arbres sont en fleurs !!!");
break;
case summer:
System.out.println("Il fait chaud !!!");
break;
case automn:
System.out.println("Les feuilles tombent...");
break;
case winter:
System.out.println("Il neige !!!");
break;
}
}
Bien entendu, les enums de Java 5.0 sont type-safe, c'est à dire qu'une enum ne peut en aucun cas prendre une autre valeur que celle définie dans sa déclaration, c'est pourquoi on ne peut ni construire de nouvelle instance, ni hériter d'une enum...
Une constante est un attribut static final qui respecte certaines conditions :
- Il doit correspondre à un type primitif (boolean, byte, char, int, long, float ou double) ou au type spécial String.
- Sa valeur doit être directement affectée en ligne à la déclaration de l'attribut.
- Sa valeur doit pouvoir être évaluée par le compilateur, c'est à dire qu'elle ne doit pas comporter de code dynamique (appel de méthode ou utilisation de variable) mais de simple opération sur des constantes.
A titre d'exemple, les attributs suivant sont des constantes :
public static final charCHAR = 'C';
public static final doubleRAYON = 15.0;
public static final doubleAIRE = Math.PI * RAYON * RAYON;
public static final StringHELLO = "Hello";
public static final StringHELLO_WORLD = HELLO + " World";
A l'inverse, les attributs suivants ne sont pas des constantes mais de simple attribut statique invariable :
public static final charCHAR = Character.valueOf('C');// Appel d'une méthode
public static final doubleRANDOM = Math.random();// Appel d'une méthode
public static final Object OBJECT = "Object";// Type incorrect (Object)
public static final StringHELLO = new String("Hello");// Utilisation d'un constructeur
public static final StringHELLO_WORLD = HELLO + " World"; // HELLO n'est pas une constante
public static final String VALUE;// Pas d'initialisation
static {
VALUE = "Value";
}
Les constantes sont traitées d'une manière particulière par le compilateur, et se rapprochent des "define" du C/C++. En effet comme le compilateur peut évaluer leurs valeurs, il ne génère pas le code d'accès à l'attribut mais le remplacera directement par sa valeur.
Prenons par exemple le code suivant :
System.out.println( Constante.HELLO + " World" );
Si Constante.HELLO est une constante, le compilateur remplacera directement l'accès à l'attribut par sa valeur, ce qui reviendrait à faire ceci :
System.out.println( "Hello" + " World" );
Et puisque le compilateur se charge d'évaluer les expressions ou sous-expressions ne contenant que des constantes, on obtient directement le code suivant :
System.out.println( "Hello World" );
Il n'y a donc plus aucune concaténation à l'exécution.
Attention toutefois, car si le code source est modifié pour changer la valeur de la constante, cette modification ne sera pas répercutée sur les autres classes si elles n'ont pas été recompilées, puisqu'elles conserveront en dur l'ancienne valeur de la constante. Du fait ce cette spécificité, il est important de ne pas utiliser abusivement les constantes pour des valeurs qui pourraient être modifiées au cours du temps, en particulier dans le cadre du développement d'une librairie.
Plutôt que d'utiliser des constantes, il est généralement préférable d'utiliser des enums ou des objets immuables. Les constantes sont utiles seulement pour des grandeurs fixes et amorphes.
Les membres d'une classe (c'est à dire ses attributs, constructeurs ou méthodes) peuvent être marqués en tant que "synthetic" par le compilateur pour indiquer qu'il s'agit d'un élément qui a été introduit par le compilateur et qui n'est donc pas directement présent dans le code source original de la classe.
La plupart du temps les membres "synthetic" sont générés lors de l'utilisation de classes interne, par exemple :
- Une classe interne non-static se verra ajouté un attribut référençant l'instance de la classe parente avec laquelle elle est liée.
- Lorsqu'une classe interne accède à une méthode ou un attribut privé de la classe parente, le compilateur ajoutera et utilisera en réalité une méthode d'accès de visibilité "package-only" dans la classe parente.
- Lorsqu'une classe interne accède à un constructeur privé de la classe parente, le compilateur génèrera en fait un autre constructeur de visibilité "package-only".
Bref toute les membres introduits par le compilateur sans correspondance dans le code source original sont marqué comme "synthetic", à l'exception toutefois des constructeurs par défaut (qui sont automatiquement rajoutés lorsque une classe ne définit aucun constructeur).
Lien : Member.isSynthetic()
Les méthodes bridges sont des méthodes "synthetics" générées par le compilateur sous certaines conditions lors de l'implémentation ou la redéfinition de méthodes paramétrées par les Generics, et que l'on spécifie un type particulier.
Prenons l'exemple d'une classe qui implémente l'interface Comparator>T< en utilisant le type String :
public class StringIgnoreCaseComparator implements Comparator<String> {
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
}
Les paramètres de la méthode compare() sont bien de type String, or puisque le type des Generics est perdu à l'exécution la méthode compare(String,String) ne respecte plus la signature de base de l'interface Comparator qui correspond plutôt à compare(Object,Object) (c'est à dire sans les types paramétrés).
Pour pallier à cela le compilateur génèrera automatiquement une méthode supplémentaire correspondant à la signature de base de la méthode, qui se contentera d'appeler la bonne méthode après un cast de ses paramètres. Ce qui donnerait dans notre cas :
public class StringIgnoreCaseComparator implements Comparator<String> {
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
// Méthode bridge créée par le compilateur :
public int compare(Object o1, Object o2) {
return compare((String)o1, (String)o2);
}
}
Afin de pouvoir être facilement repéré par l'API de réflection, ces méthodes sont marquées en tant que "bridge" par le compilateur.
Lien : [Java 5.0] Qu'est-ce que les Generics (types paramétrés) ?
Lien : Qu'est-ce qu'un membre 'synthetic' ?
Lien : Method.isBridge()


















