« Salut, Amigo ! Le thème de la leçon d'aujourd'hui est les conversions avec élargissement et réduction pour les types de référence. Tu as découvert l'élargissement et la réduction des types primitifs il y a longtemps. Au niveau 10. Aujourd'hui, nous allons parler de la façon dont cela fonctionne pour les types de référence, à savoir les instances de classes. »

En fait, tout cela est très simple. Imagine la chaîne d'héritage d'une classe : la classe, son parent, le parent du parent, etc., jusqu'à arriver à la classe Object. Comme une classe contient toutes les méthodes membres de la classe dont elle hérite, une instance de la classe peut être enregistrée dans une variable dont le type est celui de n'importe lequel de ses parents.

Voici un exemple :

Code Description
class Animal
{
public void doAnimalActions();
}class Cat extends Animal
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
Ici, nous avons trois déclarations de classe : Animal, Cat et Tiger. Cat hérite d'Animal. Et Tiger hérite de Cat.
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger();
Animal animal = new Tiger();
Object obj = new Tiger();
}
Un objet Tigre peut toujours être affecté à une variable dont le type est celui d'un de ses ancêtres. Pour la classe Tigre, il s'agit de Cat, Animal et Object.

Maintenant, nous allons nous pencher sur les conversions avec élargissement et réduction.

Si une opération d'affectation nous fait remonter la chaîne d'héritage (en direction de la classe Object), nous avons affaire à une conversion avec élargissement (ce qu'on appelle également transtypage ascendant). Si on descend la chaîne dans la direction du type de l'objet, il s'agit d'une conversion avec réduction (ce qu'on appelle également transtypage descendant).

Remonter la chaîne d'héritage est appelé élargissement, car cela conduit à un type plus général. Toutefois, ce faisant, nous perdons la possibilité d'appeler les méthodes ajoutées à la classe par héritage.

Code Description
public static void main(String[] args)
{
Object obj = new Tiger();
Animal animal = (Animal) obj;
Cat cat = (Cat) obj;
Tiger tiger = (Tiger) animal;
Tiger tiger2 = (Tiger) cat;
}
Lorsque tu réduis le type, tu dois utiliser un opérateur de conversion de type, c'est-à-dire que nous effectuons une conversion explicite.

Nous obligeons ainsi la machine Java à vérifier si l'objet hérite vraiment du type dans lequel nous voulons le convertir.

Cette petite innovation a permis une réduction considérable du nombre d'erreurs de conversion, et a augmenté de manière significative la stabilité des programmes Java.

Code Description
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
Mieux encore, utilise une vérification  instanceof
public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Animal();
doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
}

animal.doAnimalActions();
}
Et voilà pourquoi. Regarde un peu l'exemple à gauche.

Nous (et notre code) ne savons pas toujours avec quel type d'objet nous travaillons. Cela pourrait être un objet du même type que la variable (Animal), ou n'importe lequel des types descendants (Cat, Tiger).

Prenons la méthode doAllAction. Elle fonctionne correctement, quel que soit le type d'objet passé.

En d'autres termes, elle fonctionne correctement pour les trois types : Animal, Cat et Tiger.

public static void main(String[] args)
{
Cat cat = new Tiger();
Animal animal = cat;
Object obj = cat;
}
Ici, nous avons trois opérations d'affectation. Toutes sont des exemples de conversion avec élargissement.

Pas besoin d'opérateur de conversion de type ici, car aucune vérification n'est nécessaire. Une référence d'objet peut toujours être stockée dans une variable dont le type est un de ses ancêtres.

« Oh, l'avant-dernier exemple rend tout cela clair : pourquoi le contrôle est nécessaire, et pourquoi la conversion de type est nécessaire. »

« J'espère bien. Je tiens à attirer ton attention sur ce fait : »

Rien de tout cela ne cause le moindre changement pour l'objet ! La seule chose qui change est le nombre de méthodes disponibles à l'appel sur une variable de référence particulière.

Par exemple, une variable Cat te permet d'appeler les méthodes doAnimalActions et doCatActions. Elle ne sait rien au sujet de la méthode doTigerActions, même si elle pointe vers un objet Tiger.

« Oui, je comprends ça. C'est plus facile que je l'imaginais. »