Conversion de type d’objet en Java

Type d'objet Casting en Java

1. Vue d'ensemble

Le système de types Java est constitué de deux types de types: les primitives et les références.

Nous avons couvert les conversions primitives enthis article, et nous nous concentrerons sur le cast de références ici, pour bien comprendre comment Java gère les types.

Lectures complémentaires:

Les bases des génériques Java

Une introduction rapide aux bases de Java Generics.

Read more

Instance Java de l'opérateur

En savoir plus sur l'opérateur instanceof en Java

Read more

2. Primitive vs. Référence

Bien que les conversions primitives et la conversion des variables de référence puissent sembler similaires, elles sont plutôtdifferent concepts.

Dans les deux cas, nous «transformons» un type en un autre. Mais, de manière simplifiée, une variable primitive contient sa valeur et la conversion d'une variable primitive signifie des modifications irréversibles de sa valeur:

double myDouble = 1.1;
int myInt = (int) myDouble;

assertNotEquals(myDouble, myInt);

Après la conversion dans l'exemple ci-dessus, la variablemyInt est1, et nous ne pouvons pas restaurer la valeur précédente1.1 à partir de celle-ci.

Reference variables are different; la variable de référence fait uniquement référence à un objet mais ne contient pas l’objet lui-même.

Et transformer une variable de référence ne touche pas l’objet auquel il fait référence, mais ne le qualifie que d’une autre manière, en élargissant ou en réduisant les possibilités de le manipuler. Upcasting narrows the list of methods and properties available to this object, and downcasting can extend it.

Une référence est comme une télécommande à un objet. La télécommande a plus ou moins de boutons en fonction de son type et l'objet lui-même est stocké dans un tas. Lors du casting, nous modifions le type de la télécommande sans modifier l'objet lui-même.

3. Upcasting

Casting from a subclass to a superclass is called upcasting. Généralement, la conversion en amont est implicitement effectuée par le compilateur.

La diffusion ascendante est étroitement liée à l'héritage - un autre concept fondamental en Java. Il est courant d’utiliser des variables de référence pour faire référence à un type plus spécifique. Et chaque fois que nous faisons cela, une surchauffe implicite a lieu.

Pour démontrer l'upcasting, définissons une classeAnimal:

public class Animal {

    public void eat() {
        // ...
    }
}

Étendons maintenantAnimal:

public class Cat extends Animal {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

Nous pouvons maintenant créer un objet de la classeCat et l'affecter à la variable de référence de typeCat:

Cat cat = new Cat();

Et nous pouvons également l'affecter à la variable de référence de typeAnimal:

Animal animal = cat;

Dans l'assignation ci-dessus, la surchauffe implicite a lieu. Nous pourrions le faire explicitement:

animal = (Animal) cat;

Mais il n’est pas nécessaire de faire explicitement remonter l’arbre d’héritage. Le compilateur sait quecat est unAnimal et n’affiche aucune erreur.

Notez que cette référence peut faire référence à n’importe quel sous-type du type déclaré.

En utilisant la conversion ascendante, nous avons limité le nombre de méthodes disponibles à l'instance deCat, mais nous n'avons pas changé l'instance elle-même. Maintenant, nous ne pouvons rien faire de spécifique àCat –, nous ne pouvons pas invoquermeow() sur la variableanimal.

Bien que l'objetCat reste l'objetCat, l'appel demeow() provoquerait l'erreur du compilateur:

// animal.meow(); The method meow() is undefined for the type Animal

Pour invoquermeow(), nous devons abattreanimal, et nous le ferons plus tard.

Mais maintenant, nous allons décrire ce qui nous donne l’upcasting. Grâce à la conversion ascendante, nous pouvons tirer parti du polymorphisme.

3.1. Polymorphisme

Définissons une autre sous-classe deAnimal, une classeDog:

public class Dog extends Animal {

    public void eat() {
         // ...
    }
}

Nous pouvons maintenant définir la méthodefeed() qui traite tous les chats et chiens commeanimals:

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
        });
    }
}

Nous ne voulons pas queAnimalFeeder se soucie de savoir quelanimal est sur la liste - unCat ou unDog. Dans la méthodefeed(), ils sont tousanimals.

L'upcasting implicite se produit lorsque nous ajoutons des objets d'un type spécifique à la listeanimals:

List animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
new AnimalFeeder().feed(animals);

Nous ajoutons des chats et des chiens et ils sont implicitement convertis au typeAnimal. ChaqueCat est unAnimal et chaqueDog est unAnimal. Ils sont polymorphes.

Au fait, tous les objets Java sont polymorphes car chaque objet est au moins unObject. Nous pouvons attribuer une instance deAnimal à la variable de référence de typeObject et le compilateur ne se plaindra pas:

Object object = new Animal();

C’est pourquoi tous les objets Java que nous créons ont déjà des méthodes spécifiques àObject, par exemple,toString().

La diffusion en amont sur une interface est également courante.

Nous pouvons créer l'interfaceMew et faire en sorte queCat l'implémente:

public interface Mew {
    public void meow();
}

public class Cat extends Animal implements Mew {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

Désormais, tout objetCat peut également être converti enMew:

Mew mew = new Cat();

Cat est unMew, l'upcasting est légal et fait implicitement.

Ainsi,Cat est unMew,Animal,Object etCat. Il peut être affecté aux variables de référence des quatre types dans notre exemple.

3.2. Primordial

Dans l'exemple ci-dessus, la méthodeeat() est remplacée. Cela signifie que bien queeat() soit appelé sur la variable de typeAnimal, le travail est effectué par des méthodes invoquées sur des objets réels - chats et chiens:

public void feed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
    });
}

Si nous ajoutons de la journalisation à nos classes, nous verrons que les méthodes deCat etDog sont appelées:

web - 2018-02-15 22:48:49,354 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-15 22:48:49,363 [main] INFO com.example.casting.Dog - dog is eating

Pour résumer:

  • Une variable de référence peut faire référence à un objet si celui-ci est du même type qu'une variable ou s'il s'agit d'un sous-type

  • La surchauffe est implicite

  • Tous les objets Java sont polymorphes et peuvent être traités comme des objets de supertype en raison de la conversion ascendante.

4. Downcasting

Que faire si nous voulons utiliser la variable de typeAnimal pour invoquer une méthode disponible uniquement pour la classeCat? Voici le downcasting. It’s the casting from a superclass to a subclass.

Prenons un exemple:

Animal animal = new Cat();

Nous savons que la variableanimal fait référence à l'instance deCat. Et nous voulons invoquer la méthodemeow() deCat sur lesanimal. Mais le compilateur se plaint que la méthodemeow() n’existe pas pour le typeAnimal.

Pour appelermeow(), nous devons redescendreanimal versCat:

((Cat) animal).meow();

Les parenthèses internes et le type qu’elles contiennent sont parfois appelés opérateurs de conversion. Notez que des parenthèses externes sont également nécessaires pour compiler le code.

Réécrivons l'exempleAnimalFeeder précédent avec la méthodemeow():

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
            if (animal instanceof Cat) {
                ((Cat) animal).meow();
            }
        });
    }
}

Nous avons maintenant accès à toutes les méthodes disponibles pour la classeCat. Regardez le journal pour vous assurer quemeow() est réellement appelé:

web - 2018-02-16 18:13:45,445 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-16 18:13:45,454 [main] INFO com.example.casting.Cat - meow
web - 2018-02-16 18:13:45,455 [main] INFO com.example.casting.Dog - dog is eating

Notez que dans l'exemple ci-dessus, nous essayons de réduire uniquement les objets qui sont en réalité des instances deCat. Pour ce faire, nous utilisons l'opérateurinstanceof.

4.1. Opérateurinstanceof

Nous utilisons souvent l'opérateurinstanceof avant le downcasting pour vérifier si l'objet appartient au type spécifique:

if (animal instanceof Cat) {
    ((Cat) animal).meow();
}

4.2. ClassCastException

Si nous n'avions pas vérifié le type avec l'opérateurinstanceof, le compilateur ne se serait pas plaint. Mais au moment de l'exécution, il y aurait une exception.

Pour démontrer cela, supprimons l'opérateurinstanceof du code ci-dessus:

public void uncheckedFeed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
        ((Cat) animal).meow();
    });
}

Ce code est compilé sans problème. Mais si nous essayons de le lancer, nous verrons une exception:

java.lang.ClassCastException: com.example.casting.Dog ne peut pas être converti encom.example.casting.Cat

Cela signifie que nous essayons de convertir un objet qui est une instance deDog en une instance deCat.

ClassCastException's est toujours renvoyé à l'exécution si le type vers lequel nous descendons ne correspond pas au type de l'objet réel.

Notez que si nous essayons de réduire à un type non lié, le compilateur ne permettra pas ceci:

Animal animal;
String s = (String) animal;

Le compilateur dit «Impossible de convertir un animal en chaîne».

Pour que le code soit compilé, les deux types doivent figurer dans le même arbre d'héritage.

Résumons:

  • Le downcasting est nécessaire pour accéder aux membres spécifiques à la sous-classe

  • Le downcasting est fait en utilisant l'opérateur de casting

  • Pour abattre un objet en toute sécurité, nous avons besoin de l'opérateurinstanceof

  • Si l'objet réel ne correspond pas au type vers lequel nous avons réduit la conversion, alorsClassCastException sera renvoyé à l'exécution

5. MéthodeCast()

Il existe une autre façon de convertir des objets en utilisant les méthodes deClass:

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled() {
    Animal animal = new Cat();
    if (Cat.class.isInstance(animal)) {
        Cat cat = Cat.class.cast(animal);
        cat.meow();
    }
}

Dans l'exemple ci-dessus, les méthodescast() etisInstance() sont utilisées à la place des opérateurs cast etinstanceof en conséquence.

Il est courant d’utiliser les méthodescast() etisInstance() avec des types génériques.

Créons la classeAnimalFeederGeneric<T> avec la méthodefeed() qui "nourrit" un seul type d’animaux - chats ou chiens, selon la valeur du paramètre type:

public class AnimalFeederGeneric {
    private Class type;

    public AnimalFeederGeneric(Class type) {
        this.type = type;
    }

    public List feed(List animals) {
        List list = new ArrayList();
        animals.forEach(animal -> {
            if (type.isInstance(animal)) {
                T objAsType = type.cast(animal);
                list.add(objAsType);
            }
        });
        return list;
    }

}

La méthodefeed() vérifie chaque animal et ne renvoie que ceux qui sont des instances deT.

Notez que l’instanceClass doit également être transmise à la classe générique car nous ne pouvons pas l’obtenir à partir du paramètre de typeT. Dans notre exemple, nous le transmettons au constructeur.

RendonsT égal àCat et assurez-vous que la méthode ne renvoie que des chats:

@Test
public void whenParameterCat_thenOnlyCatsFed() {
    List animals = new ArrayList<>();
    animals.add(new Cat());
    animals.add(new Dog());
    AnimalFeederGeneric catFeeder
      = new AnimalFeederGeneric(Cat.class);
    List fedAnimals = catFeeder.feed(animals);

    assertTrue(fedAnimals.size() == 1);
    assertTrue(fedAnimals.get(0) instanceof Cat);
}

6. Conclusion

Dans ce didacticiel de base, nous avons exploré ce qu’est l’upcasting, le downcasting, comment les utiliser et comment ces concepts peuvent vous aider à tirer parti du polymorphisme.

Comme toujours, le code de cet article est disponibleover on GitHub.