web-dev-qa-db-fra.com

Java Class.cast () vs opérateur de distribution

Ayant appris pendant mes journées en C++ les maux de l'opérateur de conversion de style C, j'ai d'abord été ravi de constater que, dans Java 5, Java.lang.Class avait acquis une méthode cast.

Je pensais que nous avions enfin une OO manière de traiter le casting.

Class.cast n'est pas la même chose que static_cast en C++. Cela ressemble plus à reinterpret_cast. Il ne générera pas d'erreur de compilation là où il est attendu, mais sera renvoyé à l'exécution. Voici un cas de test simple pour démontrer différents comportements.

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 );
        }
    }
}

Donc, ce sont mes questions.

  1. Est-ce que Class.cast() devrait être banni 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 doit-il fournir un opérateur de distribution en tant que construction de langage similaire au C++?
93

Je n'ai jamais utilisé que Class.cast(Object) pour éviter les avertissements dans "land génériques". Je vois souvent des méthodes faire 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) que 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é de manière statique (c'est-à-dire Foo.class.cast(o) plutôt que cls.cast(o)), mais je n'ai jamais vu personne l'utiliser - ce qui rend l'effort d'intégrer cette optimisation dans le compilateur quelque peu inutile.

100
sfussenegger

Premièrement, vous êtes fortement découragé de faire presque tous les acteurs, 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() devrait être utilisé principalement lorsque vous récupérez le jeton Class par réflexion. C'est plus idiomatique d'écrire

MyObject myObject = (MyObject) object

plutôt que 

MyObject myObject = MyObject.class.cast(object)

EDIT: Erreurs lors de la compilation

Dans l'ensemble, Java effectue uniquement les vérifications de conversion au moment de l'exécution. Toutefois, le compilateur peut générer une erreur s’il peut prouver que de tels conversions ne peuvent jamais aboutir (par exemple, attribuer une classe à une autre classe qui n’est pas un supertype et attribuer un type de classe final à une classe/interface ne se trouvant pas dans sa hiérarchie). Ici, puisque Foo et Bar sont des classes qui ne sont pas dans une autre hiérarchie, la conversion ne peut jamais réussir.

17
notnoop

Essayer de traduire des constructions et des concepts entre les langues est toujours problématique et souvent trompeur. Le casting ne fait pas exception. D'autant que Java est un langage dynamique et que C++ est quelque peu différent.

Tout le casting en Java, peu importe la façon dont vous le faites, est fait à l'exécution. Les informations de type sont conservées au moment de l'exécution. C++ est un peu plus d'un mélange. Vous pouvez transtyper une structure en C++ vers une autre et il ne s'agit que d'une réinterprétation des octets représentant ces structures. Java ne fonctionne pas de cette façon.

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

16
cletus

Class.cast() est rarement utilisé dans le code Java. S'il est utilisé, alors généralement avec des types qui sont uniquement connus au moment de l'exécution (c'est-à-dire via leurs objets Class respectifs et par un paramètre de type). C'est seulement utile 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 not similaire à reinterpret_cast, car il not ne permet pas de casser le système de types au moment de l'exécution, pas plus que le transtypage normal (c'est-à-dire que vous pouvez "casser" les paramètres de type générique, mais pas "break" "vrais" types).

Les maux de l'opérateur de diffusion de style C ne s'appliquent généralement pas à Java. Le code Java qui ressemble à une distribution de style C est très similaire à dynamic_cast<>() avec un type de référence en Java (rappelez-vous: Java contient les informations de type à l'exécution).

En règle générale, il est assez difficile de comparer les opérateurs de transtypage C++ avec le transtypage Java, car en Java, vous ne pouvez jamais transtyper une référence et aucune conversion ne se produit jamais (seules les valeurs primitives peuvent être converties à l'aide de cette syntaxe).

10
Joachim Sauer

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

L'opérateur de conversion Java de style C est beaucoup plus restreint que la version C/C++. En réalité, la distribution Java est semblable à la diffusion dynamique C++ C++. Si l'objet que vous possédez ne peut pas être converti dans la nouvelle classe, vous obtiendrez une exception d'exécution (ou, si le code contient suffisamment d'informations, une exception de compilation). Ainsi, l'idée en C++ de ne pas utiliser les transtypages de type C n'est pas une bonne idée en Java

3
Mark

Généralement, l'opérateur de diffusion est préféré à la méthode de conversion Class #, car elle est plus concise et peut être analysée par le compilateur pour résoudre des problèmes flagrants avec le code.

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

Il existe certainement des cas d'utilisation pour la conversion de classe, notamment 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);
}
2
Caleb

Personnellement, je l'ai déjà utilisé pour créer un convertisseur JSON en POJO. Dans le cas où l'objet JSONObject traité avec la fonction contient un tableau ou des objets JSON imbriqués imbriqués (ce qui implique que les données ici ne sont pas d'un type primitif ou String), j'essaie d'appeler la méthode setter à l'aide de 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 suis pas sûr que cela soit extrêmement utile, mais comme je l’ai dit précédemment, la réflexion est l’un des rares cas d’utilisation légitime de class.cast() auquel je puisse penser. Au moins, vous avez maintenant un autre exemple.

0
Wep0n

En plus de supprimer les avertissements de distribution déplaisants, comme mentionné le plus souvent, Class.cast est une conversion au moment de l'exécution principalement utilisée avec la diffusion générique. En raison des informations génériques, elle sera effacée au moment de l'exécution et la manière dont chaque générique sera considéré comme un objet. jette un début ClassCastException.

par exemple, serviceLoder utilise cette astuce lors de la création des objets, vérifiez S p = service.cast (c.newInstance ()); cela lève une exception de conversion de classe lorsque S P = (S) c.newInstance (); non et peut afficher un avertissement 'Sécurité du type: transtypage non contrôlé d'objet en S' . (identique à l'objet P = (objet) c.newInstance ();)

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

Implémentation Java pour la diffusion dynamique:

@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
Amjad Abdul-Ghani