L'objet
- Entité de base manipulée par les langages orienté-objet
- Pour certains langages tout est objet (Smalltalk, Ruby...) mais en Java, distinction entre types primitifs et types objets
- Les objets sont alloués en mémoire dans le tas et sont supprimés du tas par le ramasse-miettes
Un objet sert à modéliser une réalité, il possède :
- une identité : chaque objet créé est différent
- un état qui est représenté par le contenu de ses champs
- un comportement défini par l'implantation de ses méthodes
Exemple : manipulons des points
Point p1 = new Point(0, 0); Point p2 = new Point(0, 0); Point p3 = new Point(0, 1); Pixel p4 = new Pixel(0, 1);
- Chaque objet est unique : p1, p2, p3 et p4 sont chacun stocké à un endroit différent du tas
- p1 et p2 ont le même état (contenu des champs) par contre leur identité est différente
- p1, p2 et p3 ont le même comportement car il s'agit d'instances de la même classe Point
- p3 et p4 peuvent avoir le même état avec les mêmes attributs par contre le comportement des classes Point et Pixel est possiblement différent (il faut examiner l'implantation de Point et Pixel pour le savoir)
Le modèle
Une représentation de la réalité
-
Cette représentation peut être plus ou moins utilisable selon les applications
- Il faut donc concevoir le modèle avec pour finalité une application donnée
-
Le modèle est toujours une approximation de la réalité (simplification), ne rendant pas compte de son infinie complexité
- Un modèle peut être plus précis mais au prix d'une implantation plus ardue
- Il faut trouver un compromis
- L'idéal est de partir d'un modèle grossier pour le raffiner ensuite incrémentalement
-
Le modèle ne doit pas être figé
- Il faut le concevoir avec pour principal préoccupation son évolutivité
-
Le modèle doit être réutilisable dans différents contextes
- La conception doit donc être modulaire avec une séparation en différents composants présentant un couplage faible
Différents points de vue, différents modèles
-
Modèles de la lumière en physique
- Modèle ondulatoire (peut expliquer les phénomènes d'interférence avec les fentes de Young)
- Modèle corpusculaire (peut expliquer l'effet photoélectrique avec l'existence de quantum)
-
Fonctionnement du cerveau
- Modèle de l'esprit (psychologie, psychanalyse...)
- Modèle de la matière (neurosciences : réseau neuronaux, neurobiologie...)
Selon les problèmes à résoudre, un modèle peut être privilégié plus qu'un autre (difficulté d'unifier des modèles différents).
La classe
- Classe : unité de base de code en Java et dans la plupart des langages orienté-objet
- Une classe = un fichier homonyme (suffixé en .java)
-
La classe sert à créer des objets (qui sont des instances de la classe)
- Elle sert à décrire un type d'objets avec ses différentes propriétés
-
Constituants d'une classe
- Zéro, un ou plusieurs constructeurs
- Des champs (également appelés attributs)
- Des champs statiques
- Des méthodes (appelables sur un objet)
- Des méthodes statiques (appelables sur la classe elle-même)
- Java facilite la modularité et l'extensibilité du code grâce au mécanisme d'héritage (disponible aussi dans la plupart des langages orienté-objet)
Classes et modèle
- Pour concevoir un modèle, il est possible d'élaborer un graphe de classes
- Le graphe de classes est un ensemble de classes différentes qui sont connectées entre-elles par des relations
-
Le graphe de classes est formalisé par un modèle UML (Unified Modeling Language)
- Il s'agit d'une norme de représentation d'un modèle standardisée par l'organisme Object Management Group (1ère version en 1997)
- Cela permet de représenter un modèle objet indépendamment du langage utilisé pour l'implantation
- UML permet d'appréhender un modèle objet par un diagramme de classes facilement compréhensible par un humain
Autres méthodes de modélisation
Une méthode de modélisation s'intéresse à un aspect plus ou moins spécifique du logiciel
-
Méthodes formelles permettant de définir des types, leurs relations, du pseudo-code
- Quelques exemples : Vienna Development Model (VDM), Méthode B, Méthode Z
- Intérêt : décrire un programme de façon abstraite sans considérer les spécifications d'implantation sur un langage particulier
- Structured Analysis and Design Technique (SADT) : méthode utile pour définir un workflow avec un système de boîtes multi-échelle (analyse fonctionnelle descendante) représentant entités ou activités reliées par des flèches indiquant leur relation (fourniture d'une sortie d'une fonction à l'entrée d'une autre par exemple).
-
Méthode MERISE : méthode de conception de projets étendus très populaire en France dans les années 1970-1980 plutôt adaptée à une programmation procédurale et à l'utilisation de bases de données relationnelles
- La modélisation est séparée en deux parties : la modélisation des données et des traitements que l'on réalise dessus
- Chaque partie est décomposée en trois niveaux : conceptuel, logique et physique
Intérêt de UML : modélisation graphique pour des systèmes orientés objets plus conviviale qu'une représentation formelle (mais moins précise)
Éléments essentiels d'un diagramme de classes UML
La classe
-
Une classe est visuellement représentable par un rectangle divisé verticalement en trois boîtes :
- Une classe est généralement nommée : on indique son nom
-
Une classe dispose de champs appelés attributs en UML : nous devons les spécifier
- Notation d'un attribut : <visibilité> <nom_attribut> [multiplicité]: <type_attribut> [= valeur_initialisation]
-
Une classe possède des méthodes (et des constructeurs) appelées opérations en UMV : nous les indiquons également
- Notation d'une méthode : <visibilité> <nom_methode> (<param_1>, <param_2>, ..., <param_n>): <type_de_retour>
-
Notation pour un paramètre : [direction] <nom_paramètre> : <type> [multiplicité] [= valeur par défaut]
-
La direction est facultative et prend pour valeur in (par défaut), out ou inout :
- in signifie que le paramètre est accédé uniquement en lecture
- out indique que le paramètre est accédé uniquement en écriture
- inout signifie que le paramètre est lu et écrit
- En Java, pas de valeur par défaut pour un paramètre (mais c'est possible pour d'autres langages)
-
La direction est facultative et prend pour valeur in (par défaut), out ou inout :
-
Exemple : modèle pour une voiture
- Une voiture dispose d'un kilométrage
- Nous pouvons spécifier la contenance de son réservoir ainsi que le volume actuel d'essence
- Nous pouvons démarrer la voiture...
- ...et également l'arrêter...
- ...et mettre de l'essence dans le réservoir
- Modélisons tout cela par un diagramme de classes :
- Tout modèle est une approximation grossière de la réalité : ici nous ne pouvons pas représenter des voitures comportant plusieurs réservoirs (réservoir essence + GPL), des voitures électriques...
Les relations
- Un modèle peut comporter plusieurs classes avec des relations entre-elles
- Une relation peut relier deux classes (identiques ou différentes) ou plus de classes entre-elles
- On peut annoter une relation avec un nom voire même une classe
- On peut associer à chaque participant d'une relation une multiplicité : min..max (qui peuvent être des entiers ou * pour une multiplicité non-bornée)
Relation d'association
- Une classe peut être associée à d'autres classes. On utilise pour cela une relation d'association.
- Une relation d'association est représentée par un trait plein
-
Une relation d'association peut être orientée (on utilise une flèche) ou non ; une orientation indique dans quelle direction on navigue dans l'association
- Par exemple, il est possible d'associer une personne à une adresse postale avec une orientation personne vers adresse : il est possible de consulter l'adresse d'une personne mais pas de retrouver la personne habitant à une adresse
-
Une association peut également être réflexive (une classe liée avec elle-même)
- Exemple : réalisation d'un arbre généalogique où des personnes sont liées entre-elles par des relations de parenté ou d'alliance, x est frère/soeur de y, x est l'époux de y...
- Une association peut être d'arité quelconque (au minimum 2)
Exemple : arbre généalogique (association reflexive)
Relations d'association forte
On peut renforcer la relation d'association par la composition et l'aggrégation.
Agrégation
- On ajoute un losange creux du côté de la classe représentant l'aggrégat
- Cette relation indique que l'aggrégat est une sorte d'ensemble qui comporte plusieurs sous-éléments, les éléments aggrégés : relation de contenant-contenu
- Exemple : équipement d'une salle de classe
Exemple d'association avec une voiture qui possède 4 roues voire 5 avec la roue de secours :
- On utilise une association orientée car une voiture connaît ses roues... mais l'inverse n'est pas vrai
Composition
- La composition est une relation plus forte que l'agrégation
- On utilise un losange plein du côté de l'élement container
-
L'élément container contient les éléments composés dont l'existence n'a de sens que dans le cadre de la composition
- Si l'élément container est détruit, les éléments composés doivent l'être également
- L'élément composé n'a pas de vie autonome
- Exemple : maison avec mûrs, toiture, portes, fenêtres...
Relation de dépendance
- Une classe peut dépendre d'une autre classe pour réaliser certaines actions
- La classe dépendante n'apparaît pas nécessairement comme type d'un attribut de la classe
Exemple avec une voiture ayant besoin d'une station service pour remplir son réservoir (on utilise GasStation dans l'implantation de la méthode fillTank)
- ++ Relation de généralisation/spécialisation +++umlSpecialization
- Cette relation indique qu'une classe est plus spécialisée qu'une autre classe
- La spécialisation reprend les attributs et méthodes de la classe plus générale en introduisant éventuellement des nouveautés
- Cette relation correspond au mécanisme d'héritage dans les langages objet
- Exemple avec une voiture et un vélo héritant d'un véhicule :
Relation de réalisation
- Certaines classes peuvent définir un contrat avec des méthodes à implanter (mais qui ne le sont pas encore) : il s'agit de classes abstraites ou interfaces
- Une classe qui implante des fonctionnalités d'une classe abstraite est liée par une relation de réalisation avec cette classe
- Exemple avec une voiture amphibie :
Modèle et codage
- Idéalement, on élabore en premier lieu un modèle correspondant au domaine métier abordé (par exemple sous la forme de diagramme de classes UML)
- On cherche ensuite à l'implanter à la main sous la forme de code
Du code au modèle
Il existe des outils permettant de créer des diagrammes de classes à partir de code déjà réalisé (cela ne présente pas de difficultés majeures)
Du modèle au code
Des tentatives ont été réalisées afin de générer automatiquement du code-source à partir de modèles indépendants de langages :
- plus ou moins facile s'il s'agit de générer uniquement des déclarations (de classes, de méthodes)
- beaucoup plus ardu dès qu'il s'agit de générer des implantations de méthodes (approche Model Driven Architecture)
Champs et méthodes statiques
Statique vs. non-statique
-
Non-staticité
- Le champ ou la méthode existe dans le contexte d'une instance de la classe (objet)
- Chaque instance de la classe (objet) peut avoir une valeur de champ différente
-
Staticité
- Le champ ou la méthode appartient au contexte de la classe
- Pour un champ statique, toutes les instances de la classe partagent la même valeur
Utilité des membres statiques :
- Méthode main (point d'entrée du programme) : nécessité de static car aucun objet n'a encore été alloué au démarrage du programme
- Méthodes utilitaires
- Déclaration de constantes en public static final
- Déclaration de champs statiques pour des informations de configuration du programme (variable globale)
-
En règle générale, il est préférable d'éviter les membres statiques car ils peuvent mener à des mauvaises pratiques en programmation orienté objet.
- Le non-statique est la norme, le statique l'exception
Un exemple avec des voitures
Le gouvernement de Syldavie septentrionale nous a chargé de concevoir un système d'immatriculation pour ses voitures. Chaque voiture doit avoir une immatriculation unique. Nous choisissons le schéma d'immatriculation le plus simple : chaque voiture est identifiée par un entier séquentiel (1ère voiture : 0, 2ème voiture : 1...). Nous implantons à cet effet une classe SyldavianCar :
/** A car registred in North-Syldavia */ public class SyldavianCar { /** The registration number cannot change, thus it is final */ private final int registrationNumber; /** This is the next registration number to attribute: it is static because shared for all the instances; * it is an attribute of the class itself used to construct a new instance */ private static int nextRegistrationNumber; public SyldavianCar() { this.registrationNumber = attributeRegistrationNumber(); } public int getRegistrationNumber() { return this.registrationNumber; } /** Get the next available registration number * it is private since only this current class require access to this method (used only by the constructor) */ private static int attributeRegistrationNumber() { return nextRegistrationNumber++; } }
Nous pouvons représenter la classe SyldavianCar par un modèle UML :
On remarque que les membres statiques de la classe sont soulignés dans le modèle UML.
Mécanisme d'héritage
Présentation
-
Si une classe B hérite d'une classe A :
- B est considéré comme un sous-type de A
- B hérite de tous les champs et méthodes de A
- B peut redéfinir des méthodes héritées
- B peut ajouter de nouveaux champs et nouvelles méthodes
-
En Java, on ne peut hériter que d'une classe à la fois
- Toutes les classes peuvent être organisées sous la forme d'un arbre
- La classe Object est l'ancêtre ultime de toutes les classes
- Si aucun ancêtre n'est indiqué, Object est l'ancêtre par défaut
En résumé, l'héritage recouvre deux notions :
- le sous-typage qui permet de mettre en place une hiérarchie de types
- la récupération de comportements du parent dont on hérite
Exemple de classe héritée
public class ColoredPoint extends Point { public static final Point BLACK_ORIGIN = new ColoredPoint(0, 0); private Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); // we call the constructor from the parent class this.color = color; } public ColoredPoint(int x, int y) { this(x, y, Color.BLACK); } public Color getColor() { return color; } public void setColor(Color c) { this.color = color; } }
- Nous pouvons maintenant instantier des nouveaux points colorés :
ColoredPoint cp = new ColoredPoint(0, 0, new Color(0.5, 0.5, 0.5); Point p = cp; Object o = p; ColoredPoint cp2 = (ColoredPoint)o; Point point = new Point(0, 0); ColoredPoint cp3 = (ColoredPoint)point; Parrot p = (Parrot)cp3;
- On constate qu'une variable d'un certain type T peut accueillir une référence vers un objet de type T ou d'un sous-type de T.
- Cependant, il n'est possible d'accéder qu'aux champs et méthodes correspondant au type de la variable (ainsi nous ne pourrions pas demander ni p.getColor() et a fortiori non plus o.getColor())
-
Il est possible de caster (coercition) une variable d'un type T en un sous-type
- Le compilateur ne peut déterminer statiquement si l'opération est valide
- À l'exécution, la compatibilité du cast est vérifiée : en cas d'échec une exception ClassCastException est levée
- Bien sûr, caster une variable d'un type T en un type U qui n'est ni un sous-type de T, ni un sur-type de T échouera à la compilation
A propos des constructeurs :
- Il est obligatoire d'appeler un constructeur de la classe parent au début des constructeurs de la classe enfant avec super(..)
Redéfinition
Crééons maintenant une nouvelle classe héritant de ColoredPoint pour des points ne pouvant prendre qu'une couleur grise :
public final class GrayPoint extends ColoredPoint { private float grayLevel; public GrayPoint(int x, int y, float grayLevel) { super(x, y, null); } public float getGrayLevel() { return grayLevel; } @Override public Color getColor() { return new Color(grayLevel, grayLevel, grayLevel); } }
- getColor() est un méthode redéfinie
- L'annotation @Override sert à indiquer que l'on a réalisé une redéfinition ; elle est facultative mais est conseillée afin que le compilateur vérifie que la méthode est bien redéfinie (par exemple si on avait nommé la méthode getColour(), le compilateur aurait protesté)
- En Java, toutes les méthodes sont virtuelles par défaut : c'est la méthode du type réel de l'objet (et pas du type de la variable) qui est appelée
- La redéfinition d'une méthode peut être interdite avec le mot-clé final : ainsi si on avait déclaré public final Color getColor() dans Point, nous n'aurions pas pu réaliser la redéfinition dans ColoredPoint.
Nous avons actuellement l'arbre d'héritage suivant :
Object <-- Point <-- ColoredPoint <-- GrayPoint
-
Pourrait-on créer une classe dérivée de GrayPoint ?
- Non, car on déclaré la classe GrayPoint final
- Autre exemple de classe final : la classe String
- Intérêt de final pour une classe ? Dévirtualiser les méthodes pour plus de performance.
Classe abstraite
- Une classe abstraite n'est pas instantiable directement
-
Elle peut contenir des méthodes qui sont uniquement déclarées sans être implantées (marquées abstract)
- Ces méthodes devront être implantées à un moment ou un autre dans une classe dérivée
- Une classe abstraite peut ainsi définir des méthodes implantées qui appellent des méthodes abstraites
- Une classe abstraite peut avoir des constructeurs (utilisés par les classes dérivées avec super)
Implantons une classe AbstractColoredPoint abstraite :
public abstract class AbstractColoredPoint extends Point { public AbstractColoredPoint(int x, int y) { super(x, y); } public abstract Color getColor(); public Color getNegativeColor() { final Color c = getColor(); return new Color(255 - c.getRed(), 255 - c.getGreen(), 255 - c.getBlue()); } @Override public String toString() { return "(" + x + "," + y + "," + getColor() + ")"; } }
On peut maintenant créer deux classes dérivées de AbstractColoredPoint GeneralColoredPoint pour représenter des points de couleur arbitraire et GrayColoredPoint pour représenter des points en niveau de gris :
public class GeneralColoredPoint extends AbstractColoredPoint { private Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public Color getColor() { return color; } public void setColor(Color c) { this.color = color; } }
public GrayColoredPoint extends AbstractColoredPoint { private float grayLevel; public GrayColoredPoint(int x, int y, float grayLevel) { super(x, y); this.grayLevel = grayLevel; } @Override public Color getColor() { return new Color(grayLevel, grayLevel, grayLevel); } public void setGrayLevel(float grayLevel) { this.grayLevel = grayLevel; }
-
Deux approches possibles pour les méthodes de classe dont le comportement peut changer dans les classes dérivées :
- Déclarer la méthode abstract : cela oblige la classe dérivée à implanter la méthode pour obtenir le comportement qu'elle souhaite
- Déclarer une implantation de la méthode : il s'agit d'une implantation par défaut mais la classe dérivée peut toujours la redéfinir (sauf marquage par final) si elle le souhaite
Polymorphisme (surcharge)
Écrivons maintenant une classe utilitaire avec des méthodes statiques pour réaliser des opérations sur les points :
public class Points { /** Compute a segment with all the discrete points between the two points given as arguments * @param start the start point * @param stop the stop point * @return an array including all the points of the segment */ public static Point[] computeSegment(Point start, Point stop) { int deltaX = stop.getX() - start.getX(); int deltaY = stop.getY() - start.getY(); boolean xAxis = deltaX > deltaY; // is the segment along the x axis? Point[] segment = new Point[Math.max(Math.abs(deltaX), Math.abs(deltaY)) + 1]; for (int i = 0; i < segment.length; i++) { double position = (double)i / segment.length; segment[i] = new Point(start.x + deltaX * position, start.y + deltaY * position); } return segment; } }
On souhaite maintenant écrire une méthode getSegment pour obtenir un segment entre deux points colorés avec un degradé progressif de couleur. On rajoute la méthode suivante dans la classe Points :
/** Compute a segment of interpolated points between the two points * The colors of the interpolated points follows a linear gradient */ public static AbstractColoredPoint[] computeSegment(AbstractColoredPoint start, AbstractColoredPoint stop) { Point[] uncoloredSegment = computeSegment((Point)start, (Point)stop); AbstractColoredPoint[] coloredSegment = new AbstractColoredPoint[uncoloredSegment.length]; int deltaR = stop.getColor().getRed() - start.getColor().getRed(); int deltaG = stop.getColor().getGreen() - start.getColor().getGreen(); int deltaB = stop.getColor().getBlue() - start.getColor().getBlue(); for (int i = 0; i < uncoloredSegment.length; i++) { double position = (double)i / segment.length; Point uncoloredPoint = uncoloredSegment[i]; coloredSegment[i] = new GeneralColoredPoint( uncoloredPoint.getX(), uncoloredPoint.getY(), new Color( start.getColor().getRed() + deltaR * position, start.getColor().getGreen() + deltaG * position, start.getColor().getBlue() + deltaB * position)); } }
Quel va être le résultat de ces instructions ?
Point a = new GrayColoredPoint(0, 0, 0.5); Point b = new Point(10, 10); Point c = new GeneralColoredPoint(5, 5, new Color(1.0, 0.0, 0.0)); Point[] firstSegment = Points.computeSegment(a, b); Point[] secondSegment = Points.computeSegment(a, c); Point[] thirdSegment = Points.computeSegment((GrayColoredPoint)a, (GeneralColoredPoint)c);
Écrivons maintenant dans la classe Points deux nouvelles méthodes :
- GeneralColoredPoint[] computeSegment(Point start, GeneralColoredPoint stop)
- GrayColoredPoint[] computeSegment(GrayColoredPoint start, Point stop)
Point[] segment = Points.computeSegment((GrayColoredPoint)a, (GeneralColoredPoint)c);
-
Conclusions :
- Pour sélectionner la méthode à appeler, le type des variables est utilisé (pas le type réel de l'objet)
- Cela permet de sélectionner la méthode à appeler à la compilation
- Quelquefois la sélection est impossible en cas d'ambiguïté : erreur du compilateur
Les interfaces
-
Les interfaces sont des classes spéciales dont toutes les méthodes sont abstraites
- En fait ce n'est plus tout à fait vrai depuis Java 1.8 avec les méthodes par défaut
- Une interface peut contenir des constantes déclarées public static final
- Une interface ne peut pas contenir de champ
- Une interface n'a pas de constructeur
- Une classe implantant une interface indique implements NomInterface dans sa déclaration
- Une classe peut implanter plusieurs interfaces
Définissons par exemple les interfaces suivantes :
public interface Colored { public Color getColor(); }
public interface Has2DCoordinates { public int getX(); public int getY(); }On peut alors redéclarer Point :
public class Point implements Has2DCoordinatesAinsi que AbstractColoredPoint :
public class AbstractColoredPoint extends Point implements Colored
La visibilité
Les constructeurs, champs et méthodes peuvent être accessibles ou non selon les contextes. On utilise des modificateurs de visibilité (du plus visible au moins visible) :
- public : visibilité maximale, le membre peut être utilisé depuis n'importe quelle classe (notation + en UML)
- aucun modificateur (visibilité par défaut) : accessibilité au sein du paquetage et de la descendance (notation ~ en UML)
- protected : accessibilité par les classes dérivées (notation # en UML)
- private : l'accessibilité n'est possible qu'au sein de la classe elle-même (visibilité minimale, notation - en UML)
- On choisit la visibilité la plus faible possible pour ne pas exposer inutilement un membre
- Il est préférable d'utiliser une visibilité private pour les champs et d'employer des getters et setters public
- La visibilité est augmentable par redéfinition lors d'un héritage (mais pas réductible)
La délégation et la composition
Il est possible d'implanter des fonctionnalités pour une classe :
- soit directement en ajoutant des champs de types primitifs et des méthodes pour les manipuler
- soit par héritage
- soit par composition
Par exemple, implantons GeneralColoredPoint en utilisant un mécanisme de composition :
public class GeneralColoredPoint implements Colored, Has2DCoordinates { final private Point point; final private Color color; public GeneralColoredPoint(Point point, Color color) { this.point = point; this.color = color; } @Override public Color getColor() { return color; } @Override public int getX() { return point.getX(); } @Override public int getY() { return point.getY(); } }
- Ici les méthodes getX() et getY() déléguent leur travail à Point``
- GeneralColoredPoint est composé d'un Point et d'une Color
Test du type réel à l'exécution
Random r = new Random(); Point p = null; if (r.nextInt(2) == 0) p = new Point(0, 0); else p = new GrayColoredPoint(0, 0, 0.5); // What is the real type of p?
Pour tester le type réel :
- Nous pouvons employer l'opérateur instanceof : p instanceof Point retourne true si p est un Point (ce qui est vrai dans tous les cas car GrayColoredPoint hérite de Point)
- Pour savoir si un objet a le type exact demandé (en excluant ses sous-classes) : p.getClass().equals(Point.class)
Le test de type réel d'exécution est généralement évitable (et doit être évité si possible).
Remarque : la méthode getClass() est implantée par la classe Object ; elle permet d'obtenir à l'exécution la classe d'un objet. Elle retourne une Class disposant de méthodes offrants des mécanismes d'introspection (liste de champs, méthodes, instantiation d'objets...).
Paquetages
En Java
- Les classes peuvent être organisées hiérarchiquement sous la forme d'un arbre de paquetages
- La pratique courante encourage à préfixer les paquetages par le nom de son organisation : com.company.mysoftware
- Une classe Point dans le paquetage com.company.mysoftware est dans le répertoire com/company/mysoftware
- On déclare le paquetage d'une classe en tête de fichier avec l'instruction package com.company.mysoftware;
- Pour compiler, on se place au niveau du répertoire racine des sources ; il est propre de demander la génération des fichiers .class dans un autre répertoire dédié (javac -d bin/ com/company/mysoftware/*.java)
- Pour exécuter une classe dans un paquetage, on la préfixe par le paquetage et on place la racine des classes compilées dans le classpath : java -cp bin/ com.company.mysoftware.Point
- Il est possible de spécifier un fichier de documentation pour un paquetage en y plaçant un fichier package-info.java avec pour instruction package nom.du.paquetage préfixée par une Javadoc
Dans un diagramme de classes UML
On place toutes les classes d'un paquetage dans un rectangle nommé.
Classes internes
- Il est possible de créer une classe imbriquée dans une autre classe : il s'agit d'une classe interne
-
Une classe interne peut être :
- statique si elle est déclarée avec le mot-clé static : elle peut être instantiée en dehors du contexte de sa classe englobante (si sa visibilité le permet bien sûr)
- non-statique par défaut : une référence cachée vers la classe englobante est ajoutée, elle peut être obtenue par NomClasseEnglobante.this
Quelques exemples
class A { static class B { } }
Nous pouvons instantier B en dehors de A : A.B b = new A.B();.
class A { class B { public A getSurroundingObject() { return A.this; } } }
B n'est instantiable qu'à l'intérieur d'une méthode de A.
Classes anonymes et expressions lambdas
- Quelquefois créer une classe peut être syntaxiquement lourd... surtout si l'on a besoin d'instancier une seule fois cette classe
- Une classe anonyme permet de définir et instancier en même temps une classe : le type utilisé peut être une classe existante ou même une classe abstraite ou interface (il faut alors redéfinir les méthodes nécessaires)... mais on ne peut pas implanter plusieurs classes et interfaces en même temps
-
Les interfaces ne comportant qu'une seule méthode peuvent être instantiées sous la forme d'une expression lambda (syntaxe plus légère)
- On n'écrit que le nom des arguments et le corps de la méthode : (a, b, ...) -> { ... }
Un exemple calculant la surface d'un forme
Définissons l'interface Shape définissant une zone cible avec une méthode indiquant si un point s'y trouve :
Content not available
Nous réalisons une class mesurant l'aire de la cible en testant si des points placés aléatoirement s'y trouvent :
Content not available
Nous crééons une méthode main dans une classe AreaComputerTester pour tester notre classe :
Content not available