12
votes

Avertissement "redondant cast to java.lang.Object" pour le cast nécessaire

Considérez cet exemple minimal et reproductible :

4: redundant cast to java.lang.Object

(Étant presque minimal, true est un substitut pour une expression booléenne utile. Nous pouvons ignorer au-delà du premier ? : (Dans le code réel, il y en a beaucoup).)

Cela donne «évidemment» l'erreur.

        String.valueOf((Object)(
            true ? 'a' :
            fail()
        ))

Ok, réparons-le. C'est la String.valueOf(Object) je veux - je pourrais peut-être ajouter plus tard:

            true ? "sss" :

(En fait, j'avais quelque chose de similaire plus tôt, mais j'ai maintenant supprimé la fonctionnalité.)

4: reference to valueOf is ambiguous
  both method valueOf(java.lang.Object) in java.lang.String and method valueOf(char) in java.lang.String match

Cela donne l'avertissement:

interface Code {
    static void main(String[] args) {
        symbol(
            String.valueOf(
                true ? 'a' :
                true ? 'b' :
                true ? 'c' :
                fail()
            )
        );
    }
    private static void symbol(String symbol) {
        System.out.println(symbol);
    }
    private static <R> R fail() {
        throw null;
    }
}

S'agit-il d'un bogue dans l'avertissement ou l'erreur du compilateur, et comment le corriger pour que le code soit raisonnable et qu'il n'y ait aucun avertissement ou erreur?

(Modifications: j'ai légèrement modifié le MRE. throws Throwable provient d'un modèle . Le vrai code utilise des caractères littéraux * et String.valueOf Ailleurs, il utilise la String.valueOf(char) , donc toString() est problématique (oh Java!). Le code évite l'état global, tel que System.out , et symbol et fail sont dans des classes différentes. Le "commutateur" est d'un type non énumérable. fail est un compagnon d'une méthode assert-like, donc c'est pourquoi il lève une exception (non nulle non vérifiée) en interne.

La façon dont j'ai résolu le problème, c'est que, sans aucun rapport, j'ai réorganisé le code pour qu'il y ait aussi des chaînes littérales. Sinon, j'aurais utilisé l'équivalent inutile d' Object.class.cast de (Object) . Ce que je veux vraiment savoir, c'est: wtf?

* En fait, le vrai code réel passe par un lexer pour une langue différente qui ne fait pas la distinction entre les caractères littéraux, les chaînes, les nombres divers, les booléens, les énumérations, etc. Pourquoi le ferait-il? )


6 commentaires

N'est-ce pas true ? 'a' : fail() équivalent à juste 'a' ? Quel est le but de cette construction?


@JimGarrison true est juste pour simplifier l'exemple de code. Le code réel a des lignes comme type == SymbolToken.assign ? '=' :


Je pense que vous devez fournir un exemple qui se rapproche davantage de votre cas d'utilisation. Notez que changer le 'a' en "a" supprime l'avertissement, mais je suppose que vous le savez déjà. Veuillez partager davantage votre modèle mental de ce que vous essayez d'accomplir.


@JimGarrison Ensuite, ça devient obscur. C'est un préprocesseur pour lui-même. "a " `est converti en 'a' car "abc" est indiscernable de 'abc et ainsi le préprocesseur (qui n'est qu'un trou d'arrêt) devine qu'une seule lettre est une constante char . Dans tous les cas, dans une situation équivalente, le char pourrait être retourné par une fonction aléatoire.


Eh bien, sans un exemple plus concret de votre véritable objectif, je pense que le mieux que vous allez obtenir est la distribution intégrée de char à l' Object . Cela pourrait-il être un problème XY?


@JimGarrison Non. Non, ce n'est pas le cas. Cela aiderait-il si je remplaçais le main par une méthode paramétrée avec les valeurs et en ajoutais plusieurs ? : s.


10 Réponses :


-2
votes

Si j'ignore mon exigence prospective pour String je pourrais écrire:

        String.valueOf(
            true ? 'a' :
            Code.<Object>fail()
        )

Pour gérer à la fois char et String je peux utiliser le bizarre :

    Object symbol = 
            true ? 'a' :
            fail();
    System.out.println(String.valueOf(symbol)); 

Ou utilisez la sérialisation Java pour quelque chose d'utile:

        String.valueOf((java.io.Serializable)(
            true ? 'a' :
            fail()
        ))

Cela devrait probablement être considéré comme un bogue , mais je ne peux pas être dérangé par la lutte contre bugs.java.com.

Une solution alternative consiste à introduire un local inutile:

        String.valueOf((Comparable<?>)(
            true ? 'a' :
            fail()
        ))

Cela ne devrait pas être nécessaire, mais il est possible de rendre explicite le paramètre type à fail() et d'éviter tout casts explicites désagréables:

        String.valueOf((Character)(
            true ? 'a' :
            fail()
        ))

Belle syntaxe! Sinon, fail() pourrait avoir un cast redondant ...


0 commentaires

3
votes

Le problème est que les deux branches de l'opérateur ternaire renvoient des types différents.

Que dis-tu de ça:

    System.out.println(
        String.valueOf(
            true ? (Object)'a' : fail()
        )
    );


9 commentaires

Cela devrait fonctionner, mais c'est beaucoup de code s'il y a beaucoup de lignes de ? : .


@ TomHawtin-tackline par rapport à quoi? Toutes vos alternatives contiennent au moins autant de code que cela.


@Holger Comme mentionné dans la question, je veux pouvoir en ajouter d'autres ? : s dans la chaîne (et ont dans le vrai code).


@ TomHawtin-tackline comme a? (Object)b: c? d: e? f: g ? Où est le problème?


@Holger Ne le faire que pour le premier serait bizarre, non? Surtout lorsqu'il s'agit de sélectionner la bonne surcharge.


@ TomHawtin-tackline vous changez de sujet. Mais le plus gros problème avec votre question est que vous continuez à insister sur le fait que votre cas réel est différent, mais ne montrez pas votre cas réel. Par exemple, je suppose que le cas réel String.valueOf pas String.valueOf , donc quelle méthode String.valueOf -vous réellement et doit-elle être aussi surchargée? Pourquoi la surcharge invoquée est-elle importante? Deuxièmement, votre cas réel n'est pas 'a' , alors qu'est-ce que c'est réellement? Un paramètre? Ne pouvez-vous pas le déclarer en tant Object en premier lieu? Vraiment une constante? Que diriez-vous de déclarer une constante nommée, en utilisant Object ?


@Holger Le vrai code, qui n'a pas vraiment d'importance, utilise String.valueOf , mais il n'utilise pas System.out.println ni directement dans main . Les symboles étaient des caractères littéraux entre =()[]+**/.!&|:;@?^% ( * Est la valeur pour deux conditions).


@ TomHawtin-tackline alors, je ne comprends pas vos exigences. Pourquoi est-il important d'insérer une condition? "...": transformerait le code de l'utilisation de String.value(char) à String.value(Object) ? Le comportement serait le même. Ou remplacez simplement String.valueOf par "" + et vous avez résolu le problème avec un code encore plus court. Ou, comme il n'y a pas de null impliqué, utilisez (…).toString() au lieu de String.valueOf(…) .


@Holger String.valueOf est nécessaire dans le cas où tous les littéraux sont des caractères mais le fail() reste. String.valueOf est cohérent avec le reste de la base de code. Aussi "" + regarde toujours un hack et .toString (et tout ce qui se trouve à la fin de fail() ) est un peu loin de l'action.



2
votes

Boxer explicitement le personnage est une possibilité:

class Code {
    public static void main(String[] args) throws Throwable {
        System.out.println(
                String.valueOf(
                        true ? Character.valueOf('a') : fail()
                )
        );
    }
    private static <R> R fail() {
        throw null;
    }
}


1 commentaires

Cela devrait fonctionner, mais c'est beaucoup de code s'il y a beaucoup de lignes de ? : .



12
votes

L'erreur concernant une «invocation de méthode ambiguë» est correcte depuis Java 8.

Même avant Java 8, vous pouviez écrire

System.out.println(
    String.valueOf(
        true ? 'a' :
        true ? 'b' :
        true ? 'c' :
        Optional.empty().orElseThrow(Code::fail)
    )
);

sans erreurs du compilateur. Lorsque vous passez une condition conditionnelle similaire condition? 'a': genericMethod() à une méthode comme String.valueOf(…) , le compilateur a déduit <Object> pour fail() et a choisi String.valueOf(Object) raison de son inférence de type limitée.

Mais Java 8 a introduit Poly Expressions :

Le type d'une expression autonome peut être déterminé entièrement à partir du contenu de l'expression; en revanche, le type d'une expression poly peut être influencé par le type cible de l'expression ( §5 (Conversions et contextes) ).

Les deux, une invocation d'une méthode générique et un conditionnel contenant une expression poly (c'est-à-dire l'appel d'une méthode générique), sont des expressions poly.

Donc, essayer d'appeler String.valueOf(char) est valide avec ce conditionnel, comme nous pouvons en déduire <Character> pour fail() . Notez qu'aucune méthode n'est applicable dans un contexte d'appel strict , car les deux variantes nécessitent une opération de boxing ou de déballage. Dans un contexte d'appel lâche , les deux, String.valueOf(Object) et String.valueOf(char) sont applicables, car peu importe si nous déballons le Character après avoir invoqué fail() ou encadrons le char du littéral 'a' .

Puisque char n'est pas un sous-type d' Object et que Object n'est pas un sous-type de char , ni la méthode, String.valueOf(Object) ni String.valueOf(char) , n'est plus spécifique , par conséquent, une erreur de compilation est générée.


Il est plus difficile de juger de l'avertissement, car il n'y a pas de norme formelle pour les avertissements. À mon avis, chaque avertissement du compilateur qui prétend qu'un artefact de code source était obsolète malgré le code ne fera pas la même chose après sa suppression (ou sa suppression introduira même des erreurs), est incorrect. Il est intéressant de noter que l'avertissement existe déjà dans la version Java 7 de javac , où la suppression de la distribution ne fait vraiment aucune différence, alors peut-être que ce sont des restes qui doivent être mis à jour.


Les solutions de contournement du problème dépendent du contexte et il n'y a pas suffisamment d'informations à ce sujet. Notez qu'il n'y a qu'une seule branche nécessaire qui ne soit pas assignable à char , pour rendre la méthode String.valueOf(char) inapplicable. Cela se produira, dès que vous insérez la branche qui évalue à String . Vous pouvez également utiliser SurroundingClass.<Object>fail() pour obtenir le même type que les compilateurs pré-Java 8 déduits.

Ou supprimez entièrement la signature générique, car elle n'est pas nécessaire ici. La méthode générique fail() semble être une solution de contournement pour avoir une méthode de lancement dans un contexte d'expression. Une solution plus propre serait une méthode d'usine pour l'expression, par exemple

class Code {
    public static void main(String[] args) throws SpecificExceptionType {
        System.out.println(
            String.valueOf(switch(0) {
                case 0 -> 'a';
                case 1 -> 'b';
                case 2 -> 'c';
                default -> throw fail();
            })
        );
    }
    private static SpecificExceptionType fail() {
        return new SpecificExceptionType();
    }
    static class SpecificExceptionType extends Exception {
    }
}

Si les expressions de commutation ne sont pas possibles, vous pouvez utiliser

char c = fail();
Object o = fail();

Les deux ont l'avantage d'être précis sur le type réel d'exceptions potentiellement levées et n'ont pas besoin de recourir à des exceptions non contrôlées ou de throws Throwable déclarations throws Throwable . Le second peut sembler hacky, mais pas plus que la définition d'une méthode générique qui ne renvoie jamais rien.

Bien sûr, il existe d'autres possibilités pour le résoudre, si vous acceptez simplement l'introduction de plus de code, comme une méthode d'assistance dédiée pour la conversion de chaînes sans surcharge ou une méthode wrapper non générique pour la méthode de lancement. Ou une variable temporaire ou un type casts ou des types explicites pour les invocations génériques, etc. De plus, lorsque vous utilisez "" + (expression) ou (expression).toString() au lieu de String.valueOf(expression) , l'expression n'est pas un poly expression, par conséquent, ne pas provoquer une erreur "appel de méthode ambiguë".

Bien sûr, puisqu'il s'agit d'un faux avertissement, vous pouvez également conserver le cast et ajouter un @SuppressWarnings("cast") à la méthode (et attendre que cela soit corrigé par les développeurs du compilateur).


2 commentaires

Je ne prétends pas pouvoir y suivre le JLS. Il est étrange que l'ajout d'un factice Optional.empty() fonctionne pour orElseThrow . J'espère que vous aurez le badge «gilet de sauvetage».


Pour une invocation de méthode chaînée comme .empty().orElseThrow(…) , l'inférence de type est toujours limitée pour la ou les première (s) partie (s), donc elle déduit Object ici. C'est toujours un kludge, l'expression switch est la voie à suivre, car elle permet d'intégrer la branche de lancement dans l'expression sans pseudo type de retour. Puisque la correspondance de modèle est une fonctionnalité prévue, cette solution sera applicable à d'autres conditions qu'un simple nombre à l'avenir.



1
votes

Réponse simple à une question simple:

class Code {
    
    public static void main(String[] args) throws Throwable {
        System.out.println(valueOf(
                true ? 'a' :
                true ? 'b' :
                true ? 'c' :
                fail()
            )
        );
    }
    private static <R> R fail() {
        throw null;
    }
    
    static String valueOf(Object object) {
        return String.valueOf(object);
    }
}

Ce code fonctionne tant que vous n'avez aucune valeur null . Pour couvrir également null valeurs null , vous devez introduire une méthode supplémentaire:

class Code {
    
    public static void main(String[] args) throws Throwable {
        System.out.println((
                true ? 'a' :
                true ? 'b' :
                true ? 'c' :
                fail()).toString()
        );
    }
    private static <R> R fail() {
        throw null;
    }
    
}

Les deux solutions ne nécessitent pas de modifier les nombreuses lignes de ? :

Ni l'avertissement du compilateur ni l'erreur ne sont un bogue. Dans le cas d'erreur, vous donnez au compilateur trop moins d'informations pour la sélection de la bonne méthode, dans la deuxième tentative, vous dites au compilateur de convertir un objet en un objet qui est inutile et qui vaut la peine de produire au moins un avertissement ;-)


0 commentaires

0
votes

Extrayez le résultat de l'expression dans une variable locale:

T obj =
        true ? 'a' :
                true ? 'b' :
                        true ? 'c' : fail();
System.out.println(String.valueOf(obj));


0 commentaires

0
votes

Apparemment, une bonne réponse est toujours là, mais une réponse intelligente est rare. Je pensais pouvoir dire: -

L'erreur que vous voyez est que, à un moment donné, char peut être retourné et à un moment donné, une instance de java.lang.Object peut être renvoyée. Ce qui correspond à plusieurs surcharges de String.valueOf pour provoquer l'ambiguïté.

Mais en regardant les choses de près, cela me semble être un bug. Imaginez ce qui suit: -

class Code {
    public static void main(String[] args) throws Throwable {
        System.out.println(
            String.valueOf(
                true ? (Object)'a' :
                true ? 'b' :
                true ? 'c' :
                fail())
        );
    }
    private static <R> R fail() {
        throw null;
    }
}

comme certaines surcharges de valueOf .
Essayez les brouillons suivants: -

class Code {
    public static void main(String[] args) throws Throwable {
        System.out.println(
            String.valueOf((Object)(
                true ? 'a' :
                true ? 'b' :
                true ? 'c' :
                fail())
            )
        );
    }
    private static <R> R fail() {
        throw null;
    }
}

La même erreur apparaît. Mais essayez le projet de code suivant: -

 valueOf((
                true?'a':
                true?'b':
                .
                .//a very enormous pieces of conditional branches using returning char.
                .//Note the second last element 'n', we use a cast.
                true?(Object)'n':
                fail())
            );

l'erreur disparaît.

Qu'est-il arrivé?

Comme l'une des réponses l'a suggéré, à un moment donné, le compilateur java était assez intelligent pour déduire un type uniforme pour un groupe d'instances, mais l'a perdu en cours de route.

Pourquoi appeler ça un bug

Si vous créez un arbre à partir de vos branches conditionnelles. comme:-

Diagramme de branchement conditionnel.

Ensuite, vous remarquez qu'un seul cast vers l'une des branches de gauche valide tout (aucune erreur). Le concept du compilateur semble être la dernière branche à droite qui ne soit pas analysée pour la résolution de type! (Essayez-le, c'est très étrange.) En supposant que fail() trouve dans l'une des branches de gauche, cela n'aura pas d'importance si le reposer un char ou une sorte d'objet auquel sera reduced à une résolution de java.lag.Object . La résolution de l'uniformité de type à partir de la condition afin de choisir la surcharge correcte d'une méthode est finalement vaincue. . Un bug surprenant en effet. Essayer les solutions de contournement suivantes devrait au moins éviter l'erreur dans un

  valueOf((
                true?'a':
                true?'b':
                .
                .//a very enormous pieces of conditional branches using returning char.
                .//Note the second last element 'n', we didn't cast.
                true?'n':
                fail())
            );

et celui-ci mais sans s'y limiter (vous pourriez en écrire un meilleur une fois que vous saurez comment contourner l'erreur.)

    private static char[] valueOf(String object) {return null; }
    private static char[] valueOf(char object) {return null; }
    private static char[] valueOf(Character object) {return null;}
    private static char[] valueOf(Object object) {return null;}
    private static <R> R fail() {throw null; }

Je suggérerais le premier s'il y a un besoin de lisibilité du code (au moins pour les autres développeurs).
true ici reste un «espace réservé» pour les morceaux spéculés de branches conditionnelles. Le cast box - (j'aime utiliser wrap ) chaque natif d'une instance de java.lang.Object Juste pour se conformer à ce que fail() pourrait retourner.
Remarque: un langage parfait est rare. N'ayez pas peur du bogue, s'il vous plaît.


0 commentaires

1
votes

Vous pouvez conclure l'appel avec un fournisseur pour obtenir un moyen avec:

class Code {
   public static void main(String[] args) throws Throwable {
        System.out.println(((Supplier<Object>) () ->
                true ? 'a' :
                        false ? 'b' :
                                false ? 'c' :
                                        fail()).get());
    }
    private static <R> R fail() { throw null; }
 }


1 commentaires

Ruse. Je me suis demandé s'il existe une méthode statique d'identité. new javax.swing.tree.DefaultMutableTreeNode(Object) appelle toString sur son argument pour toString . Better Object.class.cast(Object) fonctionne et est plus ou moins équivalent au code d'origine.



0
votes

'a' n'est pas une notation String ; remplacez-le par "a" (et je ne comprends pas non plus le cas d'utilisation - ou peut-être l'écrirais-je complètement différent en utilisant le moteur regex). Cependant, pour éviter les lancers inutiles ... vérifiez simplement avec instanceof ce que vous essayez même de lancer. De même, il y a peu de place pour les appels ambigus et les allers-retours inutiles.


0 commentaires

0
votes

vous n'avez pas besoin d'ajouter un cast (Object). Tout est un objet en java, pourquoi convertissez-vous de la valeur en (objet).

Object obj = (true ? 'a' : fail());
String str = String.valueOf(obj);


3 commentaires

Comme indiqué dans la question, cela donne l'erreur 4: reference to valueOf is ambiguous both method valueOf(java.lang.Object) in java.lang.String and method valueOf(char) in java.lang.String match . (Apparemment, cela provient uniquement de Java SE 8, pour toute personne derrière huit versions.)


try (true? 'a': fail ()). toString (); ajoutez également une vérification nulle. Vous pouvez également stocker le résultat dans un objet d'abord, puis appeler String.valueOf ();


Je viens de mettre à jour ma réponse, jetez un œil;