La Classe Java.cast () vs. opérateur cast


Ayant été enseigné pendant mes jours en C++ sur les maux de l'opérateur cast de style C, j'ai d'abord été heureux de constater qu'en Java 5 java.lang.Class avait acquis une méthode cast.

Je pensais que finalement nous avons une façon OO de traiter le casting.

S'avère Class.cast n'est pas le même que static_cast en C++. C'est plus comme reinterpret_cast. Il ne générera pas d'erreur de compilation là où elle est attendue et reportera plutôt à l'exécution. Voici un cas de test simple pour démontrer différents comportement.

package test;

import static org.junit.Assert.assertTrue;

import org.junit.Test;


public class TestCast
{
    static final class Foo
    {
    }

    static class Bar
    {
    }

    static final class BarSubclass
        extends Bar
    {
    }

    @Test
    public void test ( )
    {
        final Foo foo = new Foo( );
        final Bar bar = new Bar( );
        final BarSubclass bar_subclass = new BarSubclass( );

        {
            final Bar bar_ref = bar;
        }

        {
            // Compilation error
            final Bar bar_ref = foo;
        }
        {
            // Compilation error
            final Bar bar_ref = (Bar) foo;
        }

        try
        {
            // !!! Compiles fine, runtime exception
            Bar.class.cast( foo );
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }

        {
            final Bar bar_ref = bar_subclass;
        }

        try
        {
            // Compiles fine, runtime exception, equivalent of C++ dynamic_cast
            final BarSubclass bar_subclass_ref = (BarSubclass) bar;
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }
    }
}

Voici donc mes questions.

  1. Faut-il bannir Class.cast() sur les terres génériques? Là, il a quelques utilisations légitimes.
  2. Les compilateurs doivent-ils générer des erreurs de compilation lorsque Class.cast() est utilisé et que des conditions illégales peuvent être déterminées au moment de la compilation?
  3. Java devrait-il fournir un opérateur cast en tant que construction de langage similaire à C++?
Author: Alexander Pogrebnyak, 2009-10-12

8 answers

Je n'ai jamais utilisé Class.cast(Object) pour éviter les avertissements dans "generics land". Je vois souvent des méthodes faisant des choses comme ceci:

@SuppressWarnings("unchecked")
<T> T doSomething() {
    Object o;
    // snip
    return (T) o;
}

, Il est souvent préférable de le remplacer par:

<T> T doSomething(Class<T> cls) {
    Object o;
    // snip
    return cls.cast(o);
}

C'est le seul cas d'utilisation de Class.cast(Object) j'ai jamais rencontré.

Concernant les avertissements du compilateur: je soupçonne que Class.cast(Object) n'est pas spécial pour le compilateur. Il pourrait être optimisé lorsqu'il est utilisé statiquement (c'est-à-dire Foo.class.cast(o) plutôt que cls.cast(o)) mais je n'ai jamais vu personne l'utiliser-ce qui fait l'effort de construire cela optimisation dans le compilateur quelque peu sans valeur.

 122
Author: sfussenegger, 2017-05-02 06:18:31

Tout d'abord, vous êtes fortement déconseillé de faire presque n'importe quel casting, vous devez donc le limiter autant que possible! Vous perdez les avantages des fonctionnalités fortement typées de Java à la compilation.

Dans tous les cas, Class.cast() doit être utilisé principalement lorsque vous récupérez le Class jeton via la réflexion. C'est plus idiomatique d'écrire

MyObject myObject = (MyObject) object

Plutôt que

MyObject myObject = MyObject.class.cast(object)

EDIT: Erreurs au moment de la compilation

Dans l'ensemble, Java effectue des vérifications de conversion uniquement au moment de l'exécution. Cependant, le compilateur peut émettre une erreur s'il peut prouver que de tels casts ne peuvent jamais réussir (par exemple, cast une classe à une autre classe qui n'est pas un supertype et cast un type de classe final à la classe/interface qui n'est pas dans sa hiérarchie de types). Ici, puisque Foo et Bar sont des classes qui ne sont pas dans l'autre hiérarchie, la distribution ne peut jamais réussir.

 22
Author: notnoop, 2013-11-06 18:22:37

Il est toujours problématique et souvent trompeur d'essayer de traduire des constructions et des concepts entre les langues. Casting ne fait pas exception. Surtout parce que Java est un langage dynamique et que C++ est quelque peu différent.

Tout le casting en Java, peu importe comment vous le faites, se fait au moment de l'exécution. Les informations de type sont conservées lors de l'exécution. C++ est un peu plus d'un mélange. Vous pouvez convertir une structure en C++ en une autre et c'est simplement une réinterprétation des octets qui représentent ces structures. Java ne fonctionne pas de cette façon.

Les génériques en Java et C++ sont également très différents. Ne vous préoccupez pas trop de la façon dont vous faites les choses C++ en Java. Vous devez apprendre à faire les choses de la manière Java.

 17
Author: cletus, 2009-10-12 15:50:41

Class.cast() est rarement utilisé dans le code Java. S'il est utilisé alors généralement avec des types qui ne sont connus qu'au moment de l'exécution (c'est-à-dire via leurs objets Class respectifs et par un paramètre de type). Il n'est vraiment utile que dans le code qui utilise des génériques (c'est aussi la raison pour laquelle il n'a pas été introduit plus tôt).

Il est pas similaire à reinterpret_cast, car il ne vous permettra pas de casser le système de type à l'exécution plus qu'un cast normal (c'est-à-dire que vous pouvez casser type générique paramètres, mais ne peut pascasser types "réels").

Les maux de l'opérateur cast de style C ne s'appliquent généralement pas à Java. Le code Java qui ressemble à un cast de style C est le plus similaire à un dynamic_cast<>() avec un type de référence en Java (rappelez-vous: Java a des informations de type d'exécution).

Généralement, comparer les opérateurs de casting C++ avec le casting Java est assez difficile car en Java, vous ne pouvez jamais lancer de référence et aucune conversion n'arrive jamais aux objets (seules les valeurs primitives peuvent être converti en utilisant cette syntaxe).

 14
Author: Joachim Sauer, 2019-11-25 16:08:00

Généralement, l'opérateur cast est préféré à la méthode Class#cast car il est plus concis et peut être analysé par le compilateur pour cracher des problèmes flagrants avec le code.

Class#cast prend la responsabilité de la vérification de type au moment de l'exécution plutôt que pendant la compilation.

Il existe certainement des cas d'utilisation pour Class#cast, en particulier en ce qui concerne les opérations de réflexion.

Depuis que lambda est arrivé à java, j'aime personnellement utiliser Class#cast avec l'API collections/stream si Je travaille avec des types abstraits, par exemple.

Dog findMyDog(String name, Breed breed) {
    return lostAnimals.stream()
                      .filter(Dog.class::isInstance)
                      .map(Dog.class::cast)
                      .filter(dog -> dog.getName().equalsIgnoreCase(name))
                      .filter(dog -> dog.getBreed() == breed)
                      .findFirst()
                      .orElse(null);
}
 4
Author: Caleb, 2019-05-29 20:15:15

C++ et Java sont des langages différents.

L'opérateur de distribution de style C Java est beaucoup plus restreint que la version C/C++. Effectivement, le cast Java est comme le dynamic_cast C++ si l'objet que vous avez ne peut pas être cast dans la nouvelle classe, vous obtiendrez une exception d'exécution (ou s'il y a suffisamment d'informations dans le code une exception de compilation). Ainsi, l'idée C++ de ne pas utiliser de casts de type C n'est pas une bonne idée en Java

 3
Author: mmmmmm, 2009-10-12 15:57:38

En plus de supprimer les avertissements de distribution laids comme le plus mentionné, Class.cast est le cast d'exécution principalement utilisé avec le casting générique , en raison des informations génériques seront effacées au moment de l'exécution et de la façon dont chaque générique sera considéré comme un objet, cela conduit à ne pas lancer une ClassCastException précoce.

Par exemple serviceLoder utilisez cette astuce lors de la création des objets,vérifiez S p = service.cast( c. newInstance ()); cela lancera une exception de cast de classe lorsque S P =(S) c. newInstance (); ne sera pas et peut afficher un avertissement "Type de sécurité: Décoché cast d'un Objet à S'.(identique à l'objet P =(Objet) c. newInstance ();)

-il vérifie simplement que l'objet casted est une instance de la classe casting, puis il utilisera l'opérateur cast pour lancer et masquer l'avertissement en le supprimant.

Implémentation Java pour dynamic cast:

@SuppressWarnings("unchecked")
public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj;
}




    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }
 0
Author: Amjad Abdul-Ghani, 2017-12-07 16:01:55

Personnellement, je l'ai déjà utilisé pour créer un convertisseur JSON en POJO. Dans le cas où le JSONObject traité avec la fonction contient un tableau ou des JSONObjects imbriqués (ce qui implique que les données ici ne sont pas de type primitif ou String), j'essaie d'appeler la méthode setter en utilisant class.cast() de cette manière:

public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) {
    ...
    for(Method m : clazz.getMethods()) {
        if(!m.isAnnotationPresent(convertResultIgnore.class) && 
            m.getName().toLowerCase().startsWith("set")) {
        ...
        m.invoke(returnObject,  m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key))));
    }
    ...
}

Je ne sais pas si cela est extrêmement utile, mais comme dit ici précédemment, la réflexion est l'un des très rares cas d'utilisation légitime de class.cast() auquel je peux penser, au moins vous en avez un autre exemple maintenant.

 0
Author: Wep0n, 2018-03-02 09:30:17