Surcharge de Méthodes/Constructeur
Surcharge de Méthodes
Il est possible de définir plusieurs méthodes qui portent le même nom. Dans ce cas on dit qu’il y a une surcharge de méthode. Les méthodes diffère malgré tout
- Sur le nombre d’arguments
- Sur le type (et/ou l’ordre) des arguments
Si c’est le cas elles peuvent avoir des types de retour différents mais ça n’a pas vraiment de sens de faire ça.
Surcharge de Méthodes
Lorsqu’il y a une surcharge de méthode, Java tente de satisfaire l’appelle de la méthode.
public class Main {
static public void hello(String name) {
System.out.println("Hello "+name+"!!!");
}
static public void hello(String name, int age) {
System.out.println("Hello "+name+", you're "+age+" years old!!");
}
static public void hello(int age, String name) {
System.out.println("Hello "+name+", you're "+age+" years old!!");
}
public static void main(String[] args) {
hello("Alice"); // retourne "Hello Alice!!!"
hello("Bob", 38);// retourne "Hello Bob, you're 38 years old!!"
hello(38, "Bob");// retourne "Hello Bob, you're 38 years old!!"
}
}
Surcharge de Méthodes
Autre exemple: javafx.geometry.Rectangle2D
Une méthode peut appeler la methode qu’il surcharge pour évité de réécrire deux fois la même chose. Dans la classe Rectangle2D, on peut supposer que contains(Point2D p)
fait appelle à contains(double x, double y)
.
(vérifier ça!!)
Surcharge du Constructeur
Comme les autres méthodes, le constructeur peut-être surchargé (et c’est souvent le cas)
Notamment, c’est très pratique de proposer un constructeur par defaut (sans aucun paramètre) ou bien un constructeur de copy (prenant un objet de même type en paramètre).
Surcharge du Constructeur
public class Student {
private final int id;
private String name;
static private int lastStudentId;
public Student() {
this(null);
}
public Student(String name) {
this.id = lastStudentId + 1;
++lastStudentId;
if(name == null) {
name = "No Name";
}
this.name = name;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
}
Dans un constructeur, on peut appeler un autre avec l’instruction this(...)
. Dans ce case ça doit être la première instruction du constructeur.
Constructeur de Copie
class Complex {
private double re, im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
Complex(Complex c) {
re = c.re;
im = c.im;
}
public void add(Complex other) {
re += other.re;
im += other.im;
}
public void add(double re, double im) {
this.re += re;
this.im += im;
}
public String toString() {
return "(" + re + " + " + im + "i)";
}
}
public class Main {
public static void main(String[] args) {
Complex c1 = new Complex(2, 2);
Complex c2 = c1;
Complex c3 = new Complex(c1);
c1.add(3,3);
System.out.println(c2);
System.out.println(c3);
}
}
affiche : (run it!)
(5.0 + 5.0i)
(2.0 + 2.0i)
Constructeur de Copie
D’ailleurs cette classe aurait intérêt à être immuable. Et dans ce cas un constructeur de copie n’est pas utile :
final class Complex {
private final double re, im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public Complex add(Complex other) {
return add(other.re, other.im);
}
public Complex add(double re, double im) {
return new Complex(this.re + re, this.im + im);
}
public String toString() {
return "(" + re + " + " + im + "i)";
}
}
public class Main {
public static void main(String[] args) {
Complex c1 = new Complex(2, 2);
Complex c2 = c1;
c1 = c1.add(3,3);
System.out.println(c2);
}
}
Héritage
Relation entre Objets (et Classes)
- La composition/agrégation d’objets :
l’objet de type A “A UN” objet de type B - L’utilisation d’objets :
l’objet de type A “UTILISE UN” objet de type B
L’héritage introduit un nouveau type de relation :
l’objet de type B “EST UN” objet de type A
Héritage
Lorsqu’un objet de type B “EST UN” objet de type A, on dit que B hérite des propriétés de A.
- A est la super-classe (ou classe mère) de B
- B est une classe dérivée (ou sous-class ou classe fille) de A
Héritage
Lorsqu’un objet de type B “EST UN” objet de type A, on dit que B hérite des propriétés de A.
Exercice : Héritage, Agrégation ou Utilisation ?
- Cercle et Ellipse ?
- Salle de bains et Baignoire ?
- Piano et Joueur de piano ?
- Entier et Réel ?
- Personne, Enseignant et Etudiant ?
- Appareil, Imprimante, Scanner et Copieur ?
Exemple
Problèmes pour implémenter cette spécification :
Elle est trop générale : manger( ) diffère suivant les animaux ;
Il manque les services spécifiques : voler( ), nager( ), …
Mauvaise Solutions
- Faire autant de classe qu’il existe d’espèces animales :
- => beaucoup de services similaires
- => cout énorme pour maintenanir le code
- Modifier la classe Animal pour représenter tous les animaux :
- Ajouter des méthodes pour représenter toutes les choses que les animaux penvent faire
- Ajouter un attribut pour indiquer la catégorie (
isFish()
,isBird()
) - => complexité accrue, et coût énorme pour étendre le code
Solution
Utiliser une hiérarchie de classe et faire de l’héritage
Héritage
L’héritage est un concept essentiel en programmation orientée objet.
Il permet la réutiliation des composants (les classes). Il permet la création d’une classe dérivée à partir d’une classe de base et ainsi l’enrichir.
Il permet aussi la création d’une hierarchie de types (la classe dérivée définie un sous-type de la classe de base).
Héritage en Java
- Une classe hérite toujours d’une seule classe
- Par défaut, une classe hérite de la classe
Object
- Les cycles sont interdits
Héritage
Supposons qu’on propose une classe Point
qui représente un point dans le plan :
class Point {
private double x, y;
public Point() {
x = Math.random()*100;
y = Math.random()*100;
}
public Point(double x, double y)
{
this.x = x; this.y = y;
}
public void add(int dx, double dy)
{
x += dx;
y += dy;
}
public double getX() { return x; }
public double getY() { return y; }
}
Héritage
Et que l’on souhaite créer une classe ColoredPoint
qui représente un point coloré dans le plan.
Alors on peut utiliser la classe Point
et lui ajouter des propriétés. On l’indique au compilateur avec le mot clé extends
.
class ColoredPoint extends Point {
private byte color;
public void setColor(byte c) { color = c; }
public byte getColor() { return color; }
}
ColoredPoint p = new ColoredPoint();
p.setColor((byte)50);
System.out.println(p.toString() + " with color " + p.getColor());
Héritage
Une classe dérivée hérite des méthodes et attributs publics de sa classe de base. Un objet de type ColoredPoint
étant aussi de type Point
il peut donc utiliser les méthodes getX()
, getY()
en plus de getColor()
.
Attention: une classe dérivé n’a pas accés aux attributs et méthodes privés. Encore une fois, ce sont des détails d’implémentation qui ne doivent pas sortir de la classe.
Héritage et Constructeur
Dans l’exemple précédent, lorsqu’un objet de type ColoredPoint
est crée, on peut se demander si un constructeur de la class Point
est appelé.
La réponse est oui.
Juste avant que le constructeur de la classe ColoredPoint
soit appelé, un constructeur de la classe de base est appelé.
Héritage et Constructeur
Par défaut, le constructeur par défaut est appelé
Mais on peut en choisir un autre avec l’instruction super(...)
. Dans ce cas ça doit être la première instruction du constructeur
Héritage et Constructeur
class ColoredPoint extends Point {
//Choix 1
public ColoredPoint() {
color = (byte)(Math.random()*256);
}
//Choix 2
public ColoredPoint() {
super();
color = (byte)(Math.random()*256);
}
//Choix 3
public ColoredPoint() {
super(0, 0);
color = (byte)(Math.random()*256);
}
}
Ici, les choix 1 et 2 sont équivalent (c’est inutile d’écrire super()
). Par contre dans le choix 3, le constructeur de Point est appelé avec deux paramètres (les coordonnées ne sont donc plus aléatoire mais initialisé a 0).
Héritage et Constructeur
Remarque: Les contructeurs ne sont pas hérités au même titre que les méthodes publiques. Lors de la création d’un objet, un constructeur de la classe dérivée doit obligatoirement exister.
class ColoredPoint extends Point {
private byte color;
public ColoredPoint() {
color = (byte)(Math.random()*256);
}
public void setColor(byte c) { color = c; }
public byte getColor() { return color; }
}
On ne peut pas faire new ColoredPoint(10, 10)
même si un contructeur de Point
existe pour ces arguments.
bien sûr si aucun constructeur n’est défini, le compilateur en définit un par défaut qui ne fait rien.
Héritage et Constructeur
Pour être cohérent avec la classe Point
, on peut faire :
class ColoredPoint extends Point {
private byte color;
public ColoredPoint() {
color = (byte)(Math.random()*256);
}
public ColoredPoint(double x, double y) {
super(x, y);
color = (byte)(Math.random()*256);
}
public ColoredPoint(double x, double y, byte c) {
super(x, y);
color = c;
}
public void setColor(byte c) { color = c; }
public byte getColor() { return color; }
}
Exemples
class A {
public String a() { return "A"; }
}
class B {
public String b() { return "B"; }
}
B o = new B();
o.a(); //retourne "A"
o.b(); //retourne "B"
Exemples
class A {
public String a() { return "A"; }
}
class B {
private int i;
public B(int i) { this.i = i; } // Le constructeur par défaut de A est appelé
public int b() { return i; }
}
B o = new B(10);
o.a() //retourne "A"
o.b() //retourne 10
o = new B(); // Erreur de compilation
Exemples
class A {
private int j;
public A(int j) { this.j = j; }
public String a() { return "A"; }
}
class B {
private int i;
public B(int i) { this.i = i; }
public int b() { return i; }
}
Erreur de compilation, aucun constructeur par défaut n’existe pour A
Exemples
class A {
public A() { System.out.println("A created"); }
public String a() { return "A"; }
}
class B {
public String b() { return "B"; }
}
B o = new B(); // Affiche "A created"
Héritage
On peut vérifier le type d’un objet avec le mot clé instanceof
.
Point p = pointOrColoredPoint();
if(p instanceof ColoredPoint){
ColoredPoint cp = (ColoredPoint)p;
System.out.println(cp.getColor());
}
if(p instanceof Point){
Point cp = (Point)p;
System.out.println("just a point");
} else {
System.out.println("just an object");
}
Redéfinition de Méthodes
Redéfinition de Méthodes
Que ce passe-t’il lorsqu’un méthode d’une classe dérivée à exactement la même signature qu’une méthode de la classe de base?
C’est possible, et dans ce cas, c’est toujours la méthode de la classe dérivée qui est appelée.
remarque : si type de retour peut être un sous-type du type de retour de la classe de base, on parle toujours de redéfinition.
Redéfinition de Méthodes
class A {
public String foo() { return "I'm A" }
}
class B extends A {
@Override
public String foo() { return "I'm B" }
}
Le mot clé @Override
est appelé une annotation. Ici elle indique au compilateur notre intention de redéfinir une méthode qui existe dans la super-classe.
L’annotation @Override
n’est pas obligatoire mais elle permet d’être sûr qu’on n’a pas fait une faute de frappe lors de la redéfinition.
B b = new B();
b.foo(); // return "I'm B"
Mot clé super
Dans une méthode d’une classe dérivé, on peut appeler une méthode de la super-classe avec le mot clé super
class A {
public String foo() { return "I'm A" }
}
class B extends A {
@Override
public String foo() { return super.foo()+" and I'm B" }
}
B b = new B();
b.foo(); // return "I'm A and I'm B"
Surcharge de Méthodes
Si le type des arguments est différent de ceux de la classe de base, alors c’est une surcharge de méthode (comme c’est le cas au sein d’une classe).
Surcharge et Redéfinition de Méthodes
Lorsqu’une méthode est appelé sur un objet, il y a une étape de résolution pour savoir qu’elle implémentation de la méthode est utilisé :
- On considère la classe la plus dérivée dans la hiérarchie définissant l’objet (celle qui a créer l’objet avec
new
). - On regarde si la méthode existe, avec les bon arguments.
- Si c’est le cas, on utilise cette méthode, sinon on considère la classe parente (la super-classe) et on recommence au point précédent.
- Si il n’y a pas de classe parente, il y a une erreur.
Surcharge et Redéfinition de Méthodes
class A {
String foo() { return "I'm A" }
}
class B extends A {
String foo(int i) { return "I'm B" }
}
class C extends A {
String foo() { return "I'm C" }
}
class D extends C {
String foo() { return "I'm D" }
}
class E extends C {
String foo(int i) { return "I'm E" }
}
B b = new B(); C c = new C(); D d = new D(); E e = new E();
b.foo(); // return "I'm A"
b.foo(1); // return "I'm B"
c.foo(); // return "I'm C"
c.foo(1); // erreur
d.foo(); // return "I'm D"
e.foo(); // return "I'm C"
e.foo(1); // return "I'm E"
Polymorphisme
Polymorphisme
class A {
String foo() { return "I'm A" }
}
A a = generateSomething();
System.out.println(a.foo());
Considérons cet exemple, qu’affiche notre programme?
Il semblerait que ce soit “I’m A” , mais
on ne peut pas en être sûr!!
Polymorphisme
class A {
String foo() { return "I'm A" }
}
class B extends A {
String foo() { return "I'm B" }
}
A generateSomethin() { return new B(); }
A a = generateSomethin();
System.out.println(a.foo());
Dans ce cas, notre programme affiche “I’m B”. Car l’objet a
a été défini par la classe B
même s’il est stocké dans une variable de type A
. Donc lors de l’éxecution de la méthode foo
, la classe la plus dérivée (ici B) qui possède la méthode et donc utilisée.
C’est ce qu’on appelle le polymorphisme.
Polymorphisme
Le polymorphisme est le concept consistant à fournir une interface unique à des entités pouvant avoir différents types (wikipedia).
On pourra remarquer que la surcharge de méthode est aussi considéré comme du polymorphisme.
Lorsqu’il provient d’une redéfinition de méthode, on l’appelle polymorphisme par sous-typage.
Polymorphisme
En clair, ça signifie que lorsqu’on appelle une méthode sur un objet, l’implémentation choisie dépend du type réel de l’objet (la classe qui a crée l’objet) et ne dépends pas du type de la variable qui stocke l’objet.
Cela permet d’appeler la même méthode sur des objets sans se soucier de leur type!!
Polymorphisme
Cela permet d’appeler la même méthode sur des objets sans se soucier de leur type!!
Animal animals = NoahsArk.listAnimals();
for(Animal a : animals) {
a.speak();
}
Ici, on fait parler tous les animaux quelque soit leur type.
Polymorphisme
class Animal {
public void speak() { System.out.println("who am I?"); }
}
class Dog extends Animal{
public void speak() { System.out.println("I'm a dog"); }
}
class Cat extends Animal{
public void speak() { System.out.println("I'm a cat"); }
}
class Squirrel extends Animal{
public void speak() { System.out.println("I'm a squirrel"); }
}
class Dolphin extends Animal{
public void speak() { System.out.println("I'm a dolphin"); }
}
class Duck extends Animal{
public void speak() { System.out.println("I'm a duck"); }
}
class Parrot extends Animal{
public void speak() { System.out.println("I'm a parrot"); }
}
class NoahsArk {
static Animal[] listAnimals() {
Animal[] animals = new Animal[6];
animals[0] = new Dog();
animals[1] = new Squirrel();
animals[2] = new Cat();
animals[3] = new Dolphin();
animals[4] = new Duck();
animals[5] = new Parrot();
return animals;
}
}
class Main {
public static void main(String[] args) {
Animal[] animals = NoahsArk.listAnimals();
for(Animal a : animals) {
a.speak();
}
}
}
Package et Visibilité
Package (Paquetage en français)
Un paquetage est une bibliothèque d’objets, un ensemble de classes (dans un même répertoire).
Les paquetages permettent :
- de structurer le projet : les classes sont triées dans des répertoires.
- d’évité les conflits de nom : des classes de même nom peuvent exister dans des packages différents.
- d’affiner la visibilité.
Package
Pour créer un paquetage, il suffit de créer un répertoire dont le nom est en minuscule (c’est le nom du paquetage)
Pour faire partie du paquetage, une classe doit se trouver dans ce répertoire et la première ligne du fichier de la class contient le mot clé package
suivit du nom du paquetage.
Exemple, dans un fichier /..../pim/Poum.java
:
package pim;
public class Poum {
...
}
Package
Un paquetage peut contenir d’autre paquetage. On peut ainsi crée une hiérarchie complète.
Exemple, dans un fichier /..../pim/pam/Poum.java
:
package pim.pam;
public class Poum {
...
}
Il est courant dans les bibliothèques disponibles sur internet, de trouver des noms de packages contenant une url inversée. Cela permet d’éviter les collisions entre bibliothèques.
package org.json.simple; // nom du package de la libraire json-simple qui permet de parser du json
package org.json.simple.parser; // Cette bibliotèque contient un sous-package parser, ajouté à la suite de l'url inversée.
Autre exemple de paquetage javafx.scene.control
Visibilité
On a vu les mots clé public
et private
pour indiquer qu’une méthode ou un attribut est visible en dehors de la classe ou non.
On a vu aussi qu’un attribut private
est visible dans la classe mais pas dans les classes dérivées.
Visibilité – protected
Le mot clé protected
étend la visibilité aux classes dérivées.
Visibilité – par défaut
Par défaut (aucun mot clé), un attribut ou une méthode n’est visible que dans le package.
Visibilité – par défaut
Attention : la visibilité protected étant la visibilité par défaut. Donc un élément protected est accessbile par toutes les classes du paquetage et à toutes les sous-classes (même dans d’autre package)
Visibilité
- Elément private : visible uniquement au sein de la classe.
- Elément par défaut : visible en plus au sein du même paquetage.
- Elément protected : visible en plus au sein des classes dérivées.
- Elément public : visible partout.
Visibilité
Les classes dans un même répertoire sont considérées comme appartenant au même paquetage, même si celui-ci n’est pas déclaré au début du fichier avec le mot clé package
(c’est un paquetage sans nom).
Utilisation des Paquetages
Comment utiliser une classe qui se trouve dans un paquetage?
package pim.pam;
public class Poum {
...
}
En indiquand devant le nom de la classe, le nom du paquetage
public class Main {
public static void main(String[] args) {
pim.pam.Poum p = new pim.pam.Poum();
p.poum();
}
}
Utilisation des Paquetages
Ou bien, en important la classe du paquetage et ensuite utiliser la classe normalement (mot clé import
) :
import pim.pam.Poum;
public class Main {
public static void main(String[] args) {
Poum p = new Poum();
p.poum();
}
}
Import de Classes
Pour utiliser les fonctionnalités d’un package, il est souvent nécéssaire d’importer de nombreuses classes.
C’est, par exemple, le cas avec les entrées/sorties du package java.io
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.BufferedInputStream;
public class Test {
public static void main (String[] args) {
InputStream is = new InputStream();
DataInputStream dis = new DataInputStream(is);
BufferedInputStream bdis = new BufferedInputStream(dis);
...
}
}
Import de Classes
Dans ce cas, on peut importer toutes les classes d’un paquetage avec l’astérisque.
import nomPackage.*
import java.io.*;
public class Test {
public static void main (String[] args) {
InputStream is = new InputStream();
DataInputStream dis = new DataInputStream(is);
BufferedInputStream bdis = new BufferedInputStream(dis);
...
}
}
Attention : ceci n’importe pas les classes des sous-paquetages (ce n’est pas récursif).
La Classe Object
la Classe Object
On a vu que par défaut une classe hérite de la classe Object
Sinon, elle hérite d’une autre classe, qui elle hérite de la classe Object
Donc dans tous les cas, elle hérite de la classe Object
, directement ou indirectement
l’Objet Object
On a vu que toutes les classes héritent de la classe Object
.
Donc, toutes les classes héritent des méthodes publiques de cette classe. doc
Donc sur tous les objets, même sans connaitre leur type, on peut appeler ces méthodes.
Parmis ces méthodes, 3 sont particulièrement importantes.
Méthode toString()
Elle permet de représenter/décrire l’objet sous forme de chaine de charactères.
Par défaut elle affiche l’adresse mémoire de l’objet, il est important de la redéfinir au sein de chaque classe.
public class Person {
private int birthYear;
private String name;
public Person(String name, int birthYear) {
this.name = name; this.birthYear = birthYear;
}
@Override
public String toString() {
return "I'm "+name+", I'm "+getAge()+" years old.";
}
public int getAge() { return 2017 - birthYear; }
}
Méthode toString()
Object alice = new Person("Alice", 1977);
System.out.println(alice.toString());
System.out.println(alice); // Java converti automatiquement un objet en String en utilisant la méthode toString()
System.out.println("I've created an object: "+alice); // idem
System.out.println(alice.getAge()); // Erreur de compilation, la classe Object n'a pas de méthode getAge()
A la compilation, Java vérifie que la méthode appelée existe dans la classe contenant l’objet.
System.out.println(((Person)alice).getAge()); // ok
On peut convertir un objet dans un type dérivé. La compilation se passe bien. Mais pendant l’exécution, il peut se produire une erreur si l’objet n’est pas du type donné.
Méthode equals()
Elle permet de comparer deux objets. Elle retourne true
si les deux objets sont identiques.
Par défaut, cette méthode retourne true
si et seulement si les objets correspondent exactement au même objet en mémoire (ob1 == ob2
).
Lorsqu’on chosit de redéfinir cette méthode alors, intuitivement, elle doit être :
- Réflexive :
x.equals(x)
retournetrue
. - Symétrique : si
x.equals(y)
alorsy.equals(x)
. - Transitive : si
x.equals(y)
ety.equals(z)
alorsx.equals(z)
. - Cohérente : des appelles successifs retournent la même valeur.
Méthode equals()
Il est parfois intéressant de redéfinir cette méthode.
public class Person {
private int birthYear;
private String name;
public Person(String name, int birthYear) {
this.name = name; this.birthYear = birthYear;
}
@Override
public boolean equals(Object other) {
if(other == null || this.getClass() != other.getClass())
return false;
Person p = (Person)other;
return name == p.name && birthYear == p.birthYear;
}
}
Ici on considère que deux objets de type Person sont egaux si et seulement si ils ont le même nom et la même année de naissance.
Méthode equals()
Object alice = new Person("Alice", 1977);
Object aliceBis = new Person("Alice", 1977);
Object bob = new Person("Bob", 1978);
Object bobBis = bob;
alice == aliceBis // false
alice.equals(aliceBis); // true
alice.equals(bob); // false
bob == bobBis // true
bob.equals(bobBis); // true
Exemple de la classe Arrays :
“Returns true if the two specified arrays of Objects are equal to one another. The two arrays are considered equal if both arrays contain the same number of elements, and all corresponding pairs of elements in the two arrays are equal.”
Méthode hashCode()
Elle permet de représenter l’objet sous la forme d’un entier.
C’est utile pour certaines bibliothèques, qui veulent répartir des objets quelconques dans des tableau par exemple.
Il est important de redéfinir cette méthode lorsque l’on a redéfinit la méthode equals()
Surtout pour la raison suivante : La méthode hashCode()
doit retourner des valeurs identiques pour deux objets identiques (au sens de la méthode equals()
)
Méthode hashCode()
public class Person {
private int birthYear;
private String name;
public Person(String name, int birthYear) {
this.name = name; this.birthYear = birthYear;
}
@Override
public boolean equals(Object other) {
if(other == null || this.getClass() != other.getClass())
return false;
Person p = (Person)other;
return name == p.name && birthYear == p.birthYear;
}
@Override
public int hashcode() {
return 1;
}
}
Erreur de compilation : error: method does not override or implement a method from a supertype
. Sans l’annotation @Override, la compilation aurait fonctionné !!
Méthode hashCode()
public class Person {
private int birthYear;
private String name;
public Person(String name, int birthYear) {
this.name = name; this.birthYear = birthYear;
}
@Override
public boolean equals(Object other) {
if(other == null || this.getClass() != other.getClass())
return false;
Person p = (Person)other;
return name == p.name && birthYear == p.birthYear;
}
@Override
public int hashCode() {
return 1;
}
}
Cette redéfinition de hashCode()
est correcte, mais le but est d’essayer dans la mesure du possible de renvoyer des valeurs différentes pour des objets différents.
Méthode hashCode()
public class Person {
private int birthYear;
private String name;
public Person(String name, int birthYear) {
this.name = name; this.birthYear = birthYear;
}
@Override
public boolean equals(Object other) {
if(other == null || this.getClass() != other.getClass())
return false;
Person p = (Person)other;
return name == p.name && birthYear == p.birthYear;
}
@Override
public int hashCode() {
return 7 * name.hashCode() + 37 * birthYear;
}
}
Comme ça c’est mieux!!
Mot Clé final
Le mot clé final
peut être utilisé pour une classe ou une méthode.
Dans ce cas, la classe ne peut pas être dérivée, resp. la méthode ne peux pas être redéfinie.
Objets Immuables
Une classe A qui ne contient que des attributs final
et qui ne possède pas de mutateurs n’est pas forcément immuable.
En effet, lorsqu’on manipule un objet de type A, c’est peut-être en faite un objet de sous-type B, qui lui possède des attributs non final
> Pour construire des objets immuables, il faut donc une classe sans modificateurs et ne pouvant pas être dérivée (donc une classe finale)