Arnaud Labourel
16 octobre 2019
Une méthode peut utiliser les attributs et méthodes d’une autre instance/classe :
Afin d’implémenter ses services, une instance peut créer des instances et conserver leurs références dans ses attributs.
Une instance peut simplement posséder des références vers des instances :
public class Circle {
private Point center, point;
public Circle(Point center, Point point){
this.center = center;
this.point = point;
}
public Point getCenter(){ return center; }
public double getRadius(){
double dx = center.x - point.x;
double dy = center.y - point.y;
return Math.hypot(dx, dy);
}
}
Délégation du calcul de la distance à l’instance center
de la classe Point
:
Il est possible de créer des structures récursives (les attributs de la classe contiennent des références vers une instance de la classe).
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
Node d = new Node("d");
Node[] arrayAB = new Node[]{a,b};
Node ab = new Node("ab", arrayAB);
Node[] arrayCD = new Node[]{c,d};
Node cd = new Node(arrayCD, "cd");
Node cd = new Node("cd", arrayCD);
Node abcd = Node cd = new Node("abcd", arrayABCD);
Il est ensuite possible d’implémenter des méthodes de façon récursive :
ListSum
Supposons que nous ayons la classe suivante :
ListProduct
Supposons que nous ayons aussi la classe suivante (très similaire) :
Les deux classes sont très similaires et il y a 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 :
Il est possible de refactorer les classes précédentes de façon à isoler les différences dans des méthodes :
Operator
Nous allons externaliser les méthodes neutral
et compute
dans deux nouvelles classes. Elles vont implémenter l’interface Operator
:
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
:
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 :
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
}
abstract
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.
abstract
devant le nom de le type de retour de la méthode à sa définition pour signifier qu’une méthode est abstraite.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 attributs et les méthodes de List
et définir le code des méthodes abstraites :
La classe ListSum
n’est plus abstraite, toutes ses méthodes sont définies :
add
et eval
sont définies dans List
et ListSum
hérite du code de ses méthodes.neutral
et compute
qui étaient abstraites dans List
sont définies dans ListSum
.On peut donc instancier la classe ListSum
:
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 :
Plus généralement, l’extension permet de créer une classe en :
En Java :
extends
pour étendre une classe ;Il est toujours préférable de privilégier l’implémentation à l’extension.
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 :
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
:
Supposons que nous ayons la classe Point suivante :
Il est possible de redéfinir la méthode clear
dans Point
:
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 :
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 :
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
:
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 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
:
Aucune méthode ou attribut ne peut être supprimée lors d’une extension. Par exemple, Pixel possède toutes les attributs 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();
Considérer une instance d’une classe comme une instance d’une de ses super-classes, c’est-à-dire :
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 attribut est autorisé.
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.
toString()
de la classe ObjectPar transitivité de l’extension, toutes les méthodes et attributs 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 :
toString()
et le polymorphisme (1/2)Évidemment, il est possible de redéfinir la méthode toString
:
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
:
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
.
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 :
Supposons que nous avons l’interface suivante :
En Java, une interface peut étendre plusieurs interfaces :
Nous ne définissons pas de nouvelles méthodes dans l’interface ModifiablePrintableList
.
ModifiableList
et Printable
.De nouvelles méthodes auraient pu être définies dans ModifiablePrintableList
.
Une classe ou un membre (attribut ou méthode) est accessible :
public
;default
).public class MyClass {
public int myPublicField;
int myPackageField;
public void doPublicAction() { }
void doPackageAction() { }
}
Un membre (attribut ou méthode) est accessible :
public
default
)private
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 |
✓ | ✓ | ✓ | ✓ |
Comment éviter la répétition ?
Après la refactorisation du code :
neutral
etcompute
diffèrentDeux solutions :