Programmation 2 : cinquième cours
15 ou 17 octobre 2018
Classes abstraites et extension
Classe ListSum
Supposons que nous ayons la classe suivante :
public class ListSum {
private int[] list = new int[10];
private int size = 0;
public void add(int value) {
list[size] = value;
size++;
}
public int eval() {
int result = 0;
for (int i = 0; i < size; i++)
result += list[i];
return result;
}
}
Classe ListProduct
Supposons que nous ayons aussi la classe suivante (très similaire) :
public class ListProduct {
private int[] list = new int[10];
private int size = 0;
public void add(int value) {
list[size] = value;
size++;
}
public int eval() {
int result = 1;
for (int i = 0; i < size; i++)
result *= list[i];
return result;
}
}
Refactoring pour isoler la répétition
Les deux classes sont très similaires et il y a clairement de la répétition de code.
Il est possible de refactorer (réécrire le code) les classes précédentes de façon à isoler les différences dans des méthodes :
public class ListSum {
/* Propriétés, constructeur et méthode add. */
public int eval() {
int result = neutral();
for (int i = 0; i < size; i++)
result = compute(result, list[i]);
return result;
}
private int neutral() { return 0; }
private int compute(int a, int b) { return a+b; }
}
Refactoring pour isoler la répétition
Il est possible de refactorer les classes précédentes de façon à isoler les différences dans des méthodes :
public class ListProduct {
/* Propriétés, constructeur et méthode add. */
public int eval() {
int result = neutral();
for (int i = 0; i < size; i++)
result = compute(result, list[i]);
return result;
}
private int neutral() { return 1; }
private int compute(int a, int b) { return a*b; }
}
Comment éviter la répétition ?
Après la refactorisation du code :
- seules les méthodes
neutral
etcompute
diffèrent - il serait intéressant de pouvoir supprimer les duplications de code
Deux solutions :
- La délégation en utilisant une interface
- L’extension et les classes abstraites
Interface Operator
Nous allons externaliser les méthodes neutral
et compute
dans deux nouvelles classes. Elles vont implémenter l’interface Operator
:
public class Sum implements Operator {
public int neutral() { return 0; }
public int compute(int a, int b) { return a+b; }
}
public class Product implements Operator {
public int neutral() { return 1; }
public int compute(int a, int b) { return a*b; }
}
Délégation
Les classes ListSum
et ListProduct
sont fusionnées dans une unique classe List
qui délègue les calculs à un objet qui implémente l’interface Operator
:
public class List {
/* Propriétés et méthode add */
private Operator operator;
public List(Operator operator){
this.operator = operator;
}
public int eval() {
int result = operator.neutral();
for (int i = 0; i < size; i++)
result = operator.compute(result, list[i]);
return result;
}
}
Délégation
Utilisation des classes ListSum et ListProduct :
ListSum listSum = new ListSum();
listSum.add(2); listSum.add(3);
System.out.println(listSum.eval());
ListProduct listProduct = new ListProduct();
listProduct.add(2); listProduct.add(3);
System.out.println(listProduct.eval());
Utilisation après la refactorisation du code :
List listSum = new List(new Sum());
listSum.add(2); listSum.add(3);
System.out.println(listSum.eval());
List listProduct = new List(new Product());
listProduct.add(2); listProduct.add(3);
System.out.println(listProduct.eval());
Classes abstraites
public abstract class List {
private int[] list = new int[10];
private int size = 0;
public void add(int value) {
list[size] = value; size++;
}
public int eval() {
int result = neutral();
// utilisation d'une méthode abstraite
for (int i = 0; i < size; i++)
result = compute(result, list[i]); // idem
return result;
}
public abstract int neutral(); // méthode abstraite
public abstract int compute(int a, int b); // idem
}
Mot-clé abstract
Classe abstraite
- On peut mettre
abstract
devant le nom de la classe à sa définition pour signifier qu’une classe est abstraite. Une classe est abstraite si des méthodes ne sont pas implémentées.
⇒ Classe abstraite = classe avec des méthodes abstraites
Tout comme pour un interface, une classe abstraite n’est pas instanciable.
Méthode abstraite
abstract
devant le nom de le type de retour de la méthode à sa définition pour signifier qu’une méthode est abstraite.- Méthode abstraite = méthode sans code, juste la signature (type du retour et des paramètres) est définie
Classes abstraites et extension
Tout comme pour les interfaces, il n’est pas possible d’instancier une classe abstraite :
Nous allons étendre la classe List
afin de récupérer les propriétés et les méthodes de List
et définir le code des méthodes abstraites :
public class ListSum extends List {
public int neutral() { return 0; }
public int compute(int a, int b) { return a+b; }
}
Classes abstraites et extension
La classe ListSum
n’est plus abstraite, toutes ses méthodes sont définies :
- les méthodes
add
eteval
sont définies dansList
etListSum
hérite du code de ses méthodes. - les méthodes
neutral
etcompute
qui étaient abstraites dansList
sont définies dansListSum
.
On peut donc instancier la classe ListSum
:
ListSum listSum = new ListSum();
listSum.add(3); listSum.add(7);
System.out.println(listSum.eval());
Classes abstraites et extension
On peut procéder de manière similaire pour créer une classe ListProduct
public class ListProduct extends List {
public int neutral() { return 1; }
public int compute(int a, int b) { return a*b; }
}
La classe ListProduct
n’est plus abstraite, toutes ses méthodes sont définies :
ListProduct listProduct = new ListProduct();
listProduct.add(3); listProduct.add(7);
System.out.println(listProduct.eval());
Généralisation de l’extension aux classes non-abstraites
Plus généralement, l’extension permet de créer une classe en :
- conservant les services (propriétés et méthodes) d’une autre classe;
- ajoutant de nouveaux services (propriétés et méthodes);
- redéfinissant certains services (méthodes).
En Java :
- On utilise le mot-clé
extends
pour étendre une classe ; - Une classe ne peut étendre qu’une seule classe.
Important
Il est toujours préférable de privilégier l’implémentation à l’extension.
Extension pour ajouter des nouveaux services
Supposons que nous ayons la classe Point
suivante :
public class Point {
public int x, y;
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
}
Il est possible d’ajouter de nouveaux services en utilisant l’extension :
public class Pixel extends Point {
public int r, g, b;
public void setColor(int r, int g, int b) {
this.r = r; this.g = g; this.b = b; }
}
Extension pour ajouter des nouveaux services
Les services (méthodes et attributs) de Point
sont disponibles dans Pixel
:
Pixel pixel = new Pixel();
pixel.setPosition(4,8);
System.out.println(pixel.x); // → 4
System.out.println(pixel.y); // → 8
pixel.setColor(200,200,120);
Évidemment, les services de Pixel
ne sont pas disponibles dans Point
:
Point point = new Point();
point.setPosition(4,8);
System.out.println(point.x); // → 4
System.out.println(point.y); // → 8
point.setColor(200,200,120); // impossible !
Redéfinition de méthode
Supposons que nous ayons la classe Point suivante :
public class Point {
public int x, y;
public void setPosition(int x, int y) {
this.x = x; this.y = y;
}
public void clear() {
x = 0; y = 0;
}
}
Redéfinition de méthode
Il est possible de redéfinir la méthode clear
dans Point
:
public class Pixel extends Point {
public int r, g, b;
public void setColor(int r, int g, int b) {
this.r = r; this.g = g; this.b = b;
}
public void clear(){
x = 0; y = 0;
r = 0; g = 0; b = 0;
}
}
Redéfinition avec super
public class Point {
public int x, y;
public void setPosition(int x, int y) {
this.x = x; this.y = y;
}
public void clear() {
setPosition(0, 0);
}
}
Le mot-clé super
permet d’utiliser la méthode clear
de Point :
public class Pixel extends Point { public int r, g, b;
public void setColor(int r, int g, int b) {
this.r = r; this.g = g; this.b = b; }
public void clear() { super.clear(); setColor(0, 0, 0); } }
Le mot-clé super
public class Point {
public int x, y;
public void setPosition(int x, int y) {
this.x = x; this.y = y; }
}
Si la méthode n’a pas été redéfinie, le mot clé super
est inutile :
public class Pixel extends Point {
public int r, g, b;
public void setColor(int r, int g, int b) {
this.r = r; this.g = g; this.b = b;
}
public void clear() {
/*super.*/setPosition(0, 0);
setColor(0, 0, 0);
}
}
Les constructeurs et le mot-clé super
La classe Point
n’a pas de constructeur sans paramètre, il faut donc indiquer comment initialiser la partie de Pixel issue de la classe Point
:
public class Pixel extends Point {
public int r, g, b;
public Pixel(int x, int y, int r, int g, int b) {
super(x, y); // appel du constructeur de Point
this.r = r; this.g = g; this.b = b;
}
}
Les constructeurs
Supposons que nous ayons la classe Point
suivante :
public class Point {
public int x, y;
public Point() { x = 0; y = 0; }
public Point(int x, int y) { this.x = x; this.y = y; }
}
Par défaut, le constructeur sans paramètre est appelé :
public class Pixel extends Point {
public int r, g, b;
public Pixel(int r, int g, int b) {
// appel du constructeur sans paramètre de Point
this.r = r; this.g = g; this.b = b;
}
}
Les constructeurs
public class Point {
public int x, y;
//public Point() { x = 0; y = 0; }
public Point(int x, int y) { this.x = x; this.y = y; }
}
Ici, vous devez préciser les paramètres du constructeur avec super
:
public class Pixel extends Point {
public int r, g, b;
public Pixel(int r, int g, int b) {
// erreur de compilation
// (aucun constructeur sans paramètre)
this.r = r; this.g = g; this.b = b;
}
}
Transtypages et polymorphisme
Aucune méthode ou propriété ne peut être supprimée lors d’une extension. Par exemple, Pixel possède toutes les propriétés et méthodes de Point
(même si certaines méthodes ont pu être redéfinies).
Par conséquent, l’upcasting est toujours autorisé :
Point point = new Pixel();
point.setPosition(2,4);
System.out.println(point.x + " " + point.y);
point.clear();
Remarques
- Le code exécuté lors d’un appel de méthode est déterminé à l’exécution en fonction de la référence présente dans la variable.
- Le typage des variables permet de vérifier à la compilation l’existence des propriétés et des méthodes.
Upcasting
Définition d’upcasting
Considérer une instance d’une classe comme une instance d’une de ses super-classes, c’est-à-dire :
- une classe étendue par la classe
- une interface implémentée par la classe
Remarques
- Après l’upcasting, les services supplémentaires de la classe (méthodes et attributs non-disponibles dans la super-classe) ne sont plus accessibles.
- Lors de l’appel des méthodes, si la classe a redéfini (overrides) la méthode, c’est le code de la classe qui est exécuté.
La classe Object
Par défaut, les classes étendent la classe Object
de Java. Par conséquent, l’upcasting vers la classe Object
est toujours possible :
Pixel pixel = new Pixel();
Object object = pixel;
Object[] array = new Object[10];
for (int i = 0; i < t; i++) {
if (i%2==0) array[i] = new Point();
else array[i] = new Pixel();
}
Notez que object.setPosition(2,3)
n’est pas autorisé dans le code ci-dessus car la classe Object
ne possède pas la méthode setPosition
et seul le type de la variable compte pour déterminer si l’appel d’une méthode ou l’utilisation d’une propriété est autorisé.
Méthodes de la classe Object
protected Object clone()
: Creates and returns a copy of this object.boolean equals(Object obj)
: Indicates whether some other object is “equal to” this one.Class<?> getClass()
: Returns the runtime class of this Object.int hashCode()
: Returns a hash code value for the object.String toString()
: Returns a string representation of the object.
Il y a aussi des méthodes wait
et notify
pour attendre sur un objet et réveiller des thread en attentes sur un objet : cours de Programmation objet concurrente en Master 1 informatique d’AMU.
La méthode toString()
de la classe Object
Par transitivité de l’extension, toutes les méthodes et propriétés de la classe Object
sont disponibles sur toutes les instances :
Object object = new Object(); Point point = new Point(2,3);
System.out.println(object.toString());
// → java.lang.Object@19189e1
System.out.println(point.toString());
// → test.Point@7c6768
La méthode toString
est utilisée par Java pour convertir une référence en chaîne de caractères :
Object object = new Object();
Point point = new Point(2,3);
String string = object+";"+point;
System.out.println(string);
// → java.lang.Object@19189e1;test.Point@7c6768
La méthode toString()
et le polymorphisme (1/2)
Évidemment, il est possible de redéfinir la méthode toString
:
public class Point {
public int x, y;
public Point(int x, int y) {
this.x = x; this.y = y;
}
public String toString() {
return "("+x+","+y+")";
}
}
La méthode toString()
et le polymorphisme (2/2)
Le polymorphisme fait que cette méthode est appelée si la variable contient une référence vers un Point
:
Point point = new Point(2,3);
Object object = point;
System.out.println(point); // → (2,3)
System.out.println(object); // → (2,3)
Rappel : extension d’interface
Supposons que nous ayons l’interface suivante :
En Java, on peut aussi étendre une interface :
public interface ModifiableList<E> extends List<E> {
public void add(E value);
public void remove(int index);
}
Une classe qui implémente l’interface ModifiableList
doit implémenter les méthodes size
, get
, add
et remove
.
Extension d’interface
Supposons que la classe ArrayModifiableList
implémente l’interface ModifiableList
. Dans ce cas, nous pouvons écrire :
ModifiableList<Integer> modifiableList = new ArrayModifiableList<>();
modifiableList.add(2);
modifiableList.add(5);
modifiableList.remove(0);
List list = modifiableList;
System.out.println(list.size());
En revanche, il n’est pas possible d’écrire :
Extension de plusieurs interfaces
Supposons que nous avons l’interface suivante :
En Java, une interface peut étendre plusieurs interfaces :
Remarques
Nous ne définissons pas de nouvelles méthodes dans l’interface
⇒ Cette interface ne représente que l’union des interfacesModifiablePrintableList
.ModifiableList
etPrintable
.De nouvelles méthodes auraient pu être définies dans
ModifiablePrintableList
.
Extension et accessibilité
Accessibilité : modificateur public et default
Une classe ou un membre (attribut ou méthode) est accessible :
- de n’importe où s’il est précédé du modificateur
public
; - des classes de son paquet si rien n’est précisé (
default
).
public class MyClass {
public int myPublicField;
int myPackageField;
public void doPublicAction() { }
void doPackageAction() { }
}
Remarques :
- Seules les classes publiques sont utilisable à partir d’un autre paquet
- Un fichier ne peut contenir qu’une seule classe publique
Accessibilité : modificateur private
Un membre (attribut ou méthode) est accessible :
- de n’importe où s’il est précédé du modificateur
public
- des classes de son paquet si rien n’est précisé (
default
) - des méthodes de la classe s’il est précédé de
private
Afin de limiter les conséquences d’une modification :
- Les méthodes ou propriétés définies pour rendre lisible l’implémentation des fonctionnalités doivent être privées ;
- Seule l’interface de la classe doit être publique.
Accessibilité : modificateur protected
Un membre (attribut ou méthode) protected
n’est accessible que par les méthodes des classes du paquet et par les classes qui l’étendent.
Le modificateur protected permet de protéger un membre :
Modificateur | Classe | Paquet | Extension | Extérieur |
---|---|---|---|---|
private |
✓ | |||
default |
✓ | ✓ | ||
protected |
✓ | ✓ | ✓ | |
public |
✓ | ✓ | ✓ | ✓ |