web-dev-qa-db-fra.com

exemple de transtypage de type explicite en java

Je suis tombé sur cet exemple sur http://www.javabeginner.com/learn-Java/java-object-typecasting et dans la partie où il est question de transtypage de type explicite, un exemple me confond.

L'exemple:

class Vehicle {

    String name;
    Vehicle() {
        name = "Vehicle";
    }
}

class HeavyVehicle extends Vehicle {

    HeavyVehicle() {
        name = "HeavyVehicle";
    }
}

class Truck extends HeavyVehicle {

    Truck() {
        name = "Truck";
    }
}

class LightVehicle extends Vehicle {

    LightVehicle() {
        name = "LightVehicle";
    }
}

public class InstanceOfExample {

    static boolean result;
    static HeavyVehicle hV = new HeavyVehicle();
    static Truck T = new Truck();
    static HeavyVehicle hv2 = null;
    public static void main(String[] args) {
        result = hV instanceof HeavyVehicle;
        System.out.print("hV is an HeavyVehicle: " + result + "\n");
        result = T instanceof HeavyVehicle;
        System.out.print("T is an HeavyVehicle: " + result + "\n");
        result = hV instanceof Truck;
        System.out.print("hV is a Truck: " + result + "\n");
        result = hv2 instanceof HeavyVehicle;
        System.out.print("hv2 is an HeavyVehicle: " + result + "\n");
        hV = T; //Sucessful Cast form child to parent
        T = (Truck) hV; //Sucessful Explicit Cast form parent to child
    }
}

Dans la dernière ligne où T se voit attribuer la référence hV et transtypée en tant que (Camion), pourquoi est-il indiqué dans le commentaire qu'il s'agit d'une diffusion explicite réussie de parent à enfant? D'après ce que j'ai compris, transtypage (implicite ou explicite) ne modifiera que le type d'objet déclaré, et non le type réel (qui ne devrait jamais changer, à moins que vous n'affectiez réellement une nouvelle instance de classe à la référence de champ de cet objet). Si HV a déjà été affecté à une instance d'une classe HeavyVehicle qui est une super classe de la classe Truck, comment ce champ peut-il être transtypé dans une sous-classe plus spécifique appelée Truck qui s'étend de la classe HeavyVehicle?

D'après ce que j'ai compris, le transtypage sert à limiter l'accès à certaines méthodes d'un objet (instance de classe). Par conséquent, vous ne pouvez pas transformer un objet en une classe plus spécifique comportant plus de méthodes que la classe attribuée à l'objet. Cela signifie que l'objet ne peut être converti que dans une super-classe ou dans la même classe que la classe à partir de laquelle il a été instancié. Est-ce correct ou ai-je tort ici? J'apprends encore et je ne suis pas sûr que ce soit la bonne façon de voir les choses.

Je comprends aussi que cela devrait être un exemple de downcasting, mais je ne suis pas sûr de savoir comment cela fonctionne réellement si le type réel n’a pas les méthodes de la classe à laquelle cet objet est en train d’être downcasté. Le transtypage explicite modifie-t-il en quelque sorte le type réel d'objet (pas seulement le type déclaré), de sorte que cet objet n'est plus une instance de la classe HeavyVehicle mais devient maintenant une instance de la classe Truck?

31
SineLaboreNihil

Référence vs Objet vs Types

La clé, pour moi, consiste à comprendre la différence entre un objet et ses références, ou en d'autres termes, la différence entre un objet et ses types. 

Lorsque nous créons un objet en Java, nous déclarons sa vraie nature, qui ne changera jamais. Mais tout objet donné en Java est susceptible d'avoir plusieurs types. Certains de ces types sont évidemment attribués grâce à la hiérarchie des classes, d’autres ne le sont pas (par exemple, génériques, tableaux).

Spécifiquement pour les types de référence, la hiérarchie de classes dicte les règles de sous-typage. Par exemple, dans votre exemple, tous les camions sont des véhicules lourds et tous les véhicules lourds sont des véhicules. Par conséquent, cette hiérarchie de relations is-a indique qu'un camion a plusieurs types compatibles.

Lorsque nous créons une Truck, nous définissons une "référence" pour y accéder. Cette référence doit avoir l'un de ces types compatibles. 

Truck t = new Truck(); //or
HeavyVehicle hv = new Truck(); //or
Vehicle h = new Truck() //or
Object o = new Truck();

Donc, le point clé ici est la réalisation que la référence à l'objet n'est pas l'objet lui-même. La nature de l'objet en cours de création ne va jamais changer. Mais nous pouvons utiliser différents types de références compatibles pour accéder à l'objet. C'est l'une des caractéristiques du polymorphisme ici. Le même objet est accessible via des références de types "compatibles" différents. 

Lorsque nous faisons un casting, nous supposons simplement la nature de cette compatibilité entre différents types de références.

Conversion de référence ascendante ou élargie

Maintenant, ayant une référence de type Truck, on peut facilement en conclure qu'il est toujours compatible avec une référence de type Vehicle, car tous les camions sont des véhicules. Par conséquent, nous pourrions convertir la référence en amont, sans utiliser de conversion explicite.

Truck t = new Truck();
Vehicle v = t;

Il est également appelé une conversion de référence d'élargissement , essentiellement parce qu'au fur et à mesure que vous montez dans la hiérarchie des types, le type devient plus général. 

Vous pouvez utiliser une distribution explicite ici si vous le souhaitez, mais ce serait inutile. Nous pouvons voir que l'objet réel référencé par t et v est le même. C'est et sera toujours une Truck.

Downcasting ou conversion de référence restrictive

Maintenant, ayant une référence de type Vechicle, nous ne pouvons pas conclure "en toute sécurité" qu'il fait en réalité référence à Truck. Après tout, il peut également faire référence à une autre forme de véhicule. Par exemple

Vehicle v = new Sedan(); //a light vehicle

Si vous trouvez la référence v quelque part dans votre code sans savoir à quel objet elle fait référence, vous ne pouvez pas argumenter en toute sécurité, qu'il pointe vers une Truck ou une Sedan ou tout autre type de véhicule.

Le compilateur sait bien qu'il ne peut donner aucune garantie sur la vraie nature de l'objet référencé. Mais le programmeur, en lisant le code, peut être sûr de ce qu'il fait. Comme dans le cas ci-dessus, vous pouvez clairement voir que Vehicle v fait référence à une Sedan.

Dans ces cas, nous pouvons faire un downcast. Nous l'appelons ainsi parce que nous descendons dans la hiérarchie des types. Nous appelons également cela une conversion de référence restrictive . On pourrait dire

Sedan s = (Sedan) v;

Cela nécessite toujours une conversion explicite, car le compilateur ne peut pas être sûr que cela est sûr et c'est pourquoi c'est comme demander au programmeur, "êtes-vous sûr de ce que vous faites?". Si vous mentez au compilateur, vous obtiendrez une ClassCastException au moment de l'exécution, lorsque ce code sera exécuté.

Autres types de règles de sous-typage

Il existe d'autres règles de sous-typage en Java. Par exemple, il existe également un concept appelé promotion numérique qui contraint automatiquement les nombres dans les expressions. Comme dans

double d = 5 + 6.0;

Dans ce cas, une expression composée de deux types différents, entier et double, monte/décale/convertit l'entier en un double avant d'évaluer l'expression, ce qui donne une valeur double.

Vous pouvez également faire des conversions ascendantes et descendantes primitives. Un péché

int a = 10;
double b = a; //upcasting
int c = (int) b; //downcasting

Dans ces cas, une conversion explicite est requise lorsque des informations peuvent être perdues. 

Certaines règles de sous-typage peuvent ne pas être aussi évidentes, comme dans le cas des tableaux. Par exemple, tous les tableaux de référence sont des sous-types de Object[], mais pas les tableaux primitifs.

Et dans le cas des génériques, en particulier avec l'utilisation de caractères génériques tels que super et extends, les choses deviennent encore plus compliquées. Comme dans

List<Integer> a = new ArrayList<>();
List<? extends Number> b = a;

List<Object> c = new ArrayList<>(); 
List<? super Number> d = c;

Où le type de b est un sous-type du type de a. Et le type de d est un sous-type du type de c.

Et aussi la boxe et le déballage sont soumis à des règles de casting (encore une fois c'est aussi une forme de coercition à mon avis).

60
Edwin Dalorzo

Vous avez bien compris. Vous ne pouvez convertir un objet que vers sa classe, certaines de ses classes parentes ou une interface implémentée par elle ou ses parents. Si vous le transmettez à certaines des classes ou interfaces parent, vous pouvez le rétablir dans le type d'origine.

Sinon (tant que vous pouvez l'avoir dans le source), cela entraînera une exception ClassCastException au moment de l'exécution.

Le casting est généralement utilisé pour permettre de stocker différentes choses (de la même interface ou classe parent, par exemple toutes vos voitures) dans le même champ ou une collection du même type (par exemple, Véhicule), afin que vous puissiez travailler avec de la même manière.

Si vous souhaitez ensuite obtenir un accès complet, vous pouvez les rejeter (par exemple, véhicule à camion). 


Dans l'exemple, je suis à peu près sûr que la dernière déclaration est invalide et que le commentaire est tout simplement faux.

5
MightyPork

La dernière ligne de code est compilée et fonctionne correctement sans exception. Ce qu'il fait est parfaitement légal.

  1. hV fait initialement référence à un objet de type HeavyVehicle (appelons cet objet h1):

    static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
    
  2. Ensuite, nous ferons référence à un objet différent, de type Truck (appelons cet objet t1):

    hV = T; // hV now refers to t1.
    
  3. Enfin, nous faisons référence à t1.

    T = (Truck) hV; // T now refers to t1.
    

T déjà référé à t1, donc cette déclaration n'a rien changé.

Si HV a déjà été affecté à une instance d'une classe HeavyVehicle qui est une super classe de la classe Truck, comment ce champ peut-il être transtypé dans une sous-classe plus spécifique appelée Truck qui s'étend de la classe HeavyVehicle?

Au moment où nous atteignons la dernière ligne, HV ne fait plus référence à un exemple de HeavyVehicle. Il fait référence à une instance de Truck. Lancer une instance de Truck dans Truck n'est pas un problème.

Cela signifie que l'objet ne peut être converti que dans une super-classe ou dans la même classe que la classe à partir de laquelle il a été instancié. Est-ce correct ou ai-je tort ici?

En gros, oui, mais ne confondez pas l'objet lui-même avec une variable qui fait référence à l'objet. Voir ci-dessous.

Le transtypage explicite modifie-t-il en quelque sorte le type réel d'objet (pas seulement le type déclaré), de sorte que cet objet n'est plus une instance de la classe HeavyVehicle mais devient maintenant une instance de la classe Truck?

Non. Un objet créé ne peut jamais changer de type. Cela ne peut pas devenir une instance d'une autre classe.

Pour rappel, rien n'a changé à la dernière ligne. T a fait référence à t1 avant cette ligne et il fait référence à t1 après.

Alors, pourquoi la distribution explicite (Truck) est-elle nécessaire sur la dernière ligne? Nous aidons essentiellement à aider le compilateur.

Nous savons que, à ce stade, hV fait référence à un objet de type Truck. Il est donc correct d'affecter cet objet de type Truck à la variable T. Mais le compilateur n'est pas assez intelligent pour le savoir. . Le compilateur veut avoir l’assurance que lorsqu’il arrivera sur cette ligne et essaiera de faire l’affectation, il trouvera une instance de Truck l’attendant.

2
dshiga

Lorsque vous transformez un objet Truck en un casting avec un HeavyVehicle: 

Truck truck = new Truck()
HeavyVehicle hv = truck;

L'objet est toujours un camion, mais vous avez uniquement accès aux méthodes et aux champs heavyVehicle utilisant la référence HeavyVehicle. Si vous redescendez vers un camion, vous pouvez utiliser à nouveau toutes les méthodes et tous les champs du camion.

Truck truck = new Truck()
HeavyVehicle hv = truck;
Truck anotherTruckReference = (Truck) hv; // Explicit Cast is needed here

Si l'objet réel que vous êtes en train de downcasting n'est pas un camion, une exception ClassCastException sera levée comme dans l'exemple suivant:

HeavyVehicle hv = new HeavyVehicle();
Truck tr = (Truck) hv;  // This code compiles but will throw a ClasscastException

L'exception est levée car l'objet réel n'est pas de la classe correcte, c'est un objet d'une super-classe (HeavyVehicle)

2
David SN

Le code ci-dessus va compiler et fonctionner correctement. Maintenant, changez le code ci-dessus et ajoutez la ligne suivante System.out.println (T.name);

Cela garantira que vous n'utilisez pas l'objet T après avoir descendu l'objet hV en tant que camion.

Actuellement, dans votre code, vous n'utilisez pas T après downcast, donc tout va bien et fonctionne. 

En effet, en diffusant explicitement hV en tant que Truck, Complier se plaint de considérer que le programmeur a jeté l'objet et qu'il est conscient de l'objet qui a été jeté à quoi.

Mais au moment de l’exécution, la machine virtuelle Java n’est pas en mesure de justifier le casting et jette ClassCastException "HeavyVehicle ne peut pas être jeté sur Truck".

1
Prashant Thakkar

Pour aider à mieux illustrer certains des points évoqués ci-dessus, j'ai modifié le code en question et y a ajouté des codes avec des commentaires intégrés (y compris les résultats réels) comme suit: 

class Vehicle {

        String name;
        Vehicle() {
                name = "Vehicle";
        }
}

class HeavyVehicle extends Vehicle {

        HeavyVehicle() {
                name = "HeavyVehicle";
        }
}

class Truck extends HeavyVehicle {

        Truck() {
                name = "Truck";
        }
}

class LightVehicle extends Vehicle {

        LightVehicle() {
                name = "LightVehicle";
        }
}

public class InstanceOfExample {

        static boolean result;
        static HeavyVehicle hV = new HeavyVehicle();
        static Truck T = new Truck();
        static HeavyVehicle hv2 = null;
        public static void main(String[] args) {

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true

                result = T instanceof HeavyVehicle;
                System.out.print("T is a HeavyVehicle: " + result + "\n"); // true
//      But the following is in error.              
//      T = hV; // error - HeavyVehicle cannot be converted to Truck because all hV's are not trucks.                               

                result = hV instanceof Truck;
                System.out.print("hV is a Truck: " + result + "\n"); // false               

                hV = T; // Sucessful Cast form child to parent.
                result = hV instanceof Truck; // This only means that hV now points to a Truck object.                            
                System.out.print("hV is a Truck: " + result + "\n");    // true         

                T = (Truck) hV; // Sucessful Explicit Cast form parent to child. Now T points to both HeavyVehicle and Truck. 
                                // And also hV points to both Truck and HeavyVehicle. Check the following codes and results.
                result = hV instanceof Truck;                             
                System.out.print("hV is a Truck: " + result + "\n");    // true 

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true             

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true 

                result = hv2 instanceof HeavyVehicle;               
                System.out.print("hv2 is a HeavyVehicle: " + result + "\n"); // false

        }

}
0
S. W. Chi