2010-03-16 3 views
2
import java.lang.reflect.Array; 

public class PrimitiveArrayGeneric { 
    static <T> T[] genericArrayNewInstance(Class<T> componentType) { 
     return (T[]) Array.newInstance(componentType, 0); 
    } 

    public static void main(String args[]) { 
     int[] intArray; 
     Integer[] integerArray; 

     intArray = (int[]) Array.newInstance(int.class, 0); 
     // Okay! 

     integerArray = genericArrayNewInstance(Integer.class); 
     // Okay! 

     intArray = genericArrayNewInstance(int.class); 
     // Compile time error: 
      // cannot convert from Integer[] to int[] 

     integerArray = genericArrayNewInstance(int.class); 
     // Run time error: 
      // ClassCastException: [I cannot be cast to [Ljava.lang.Object; 
    }  
} 

J'essaie de comprendre complètement comment les génériques fonctionnent en Java. Les choses deviennent un peu bizarres pour moi dans la troisième affectation dans l'extrait ci-dessus: le compilateur se plaint que Integer[] ne peut pas être converti en int[]. La déclaration est 100% vrai, bien sûr, mais je me demande POURQUOI le compilateur fait cette plainte.Comportement bizarre à la compilation lors de la tentative d'utilisation du type primitif dans les génériques

Si vous commentez cette ligne, et suivez la "suggestion" du compilateur comme dans la 4ème affectation, le compilateur est réellement satisfait !!!maintenant le code se compile très bien! Ce qui est fou, bien sûr, puisque comme le comportement de temps d'exécution suggère, int[] ne peut pas être converti en Object[] (qui est ce que T[] est effacé lors de l'exécution).

Donc ma question est: pourquoi le compilateur "suggère" que j'attribue à Integer[] à la place pour la 3ème affectation? Comment le compilateur raisonne-t-il pour arriver à cette conclusion (erronée!)?


Il y a beaucoup de confusion dans les deux réponses à ce jour, donc je créé un autre exemple déconcertant pour illustrer la question sous-jacente:

public class PrimitiveClassGeneric {  
    static <T extends Number> T test(Class<T> c) { 
     System.out.println(c.getName() + " extends " + c.getSuperclass()); 
     return (T) null; 
    } 
    public static void main(String args[]) { 
     test(Integer.class); 
     // "java.lang.Integer extends class java.lang.Number" 

     test(int.class); 
     // "int extends null" 
    } 
} 

Suis-je le seul qui pense qu'il est absolument fou que le compilateur laisse compiler le code ci-dessus?

Il ne serait pas déraisonnable d'imprimer c.getSuperclass().getName() dans le code ci-dessus, par exemple, puisque j'ai spécifié que T extends Number. Bien sûr maintenant getName() jettera NullPointerException quand c == int.class, depuis c.getSuperclass() == null.

Et pour moi, c'est une très bonne raison de rejeter le code de la compilation en premier lieu.


Peut-être la folie ultime:

int.class.cast(null); 

Ce code se compile ET fonctionne bien.

Répondre

4

Le type de int.class est Class<Integer>, donc genericArrayNewInstance() serait déduit pour retourner un Integer[]. Mais la fonction crée en réalité un int[], donc il aurait une exception de cast de classe quand il est retourné. Fondamentalement, le cast à T[] à l'intérieur de la fonction n'est pas légitime dans ce cas, car int[] n'est pas un T[] (les primitives ne peuvent pas être utilisées dans les variables de type). Vous ne pouvez pas gérer les types de tableaux primitifs de manière générique; donc vous devez avoir votre méthode juste retourner le type Object, ou vous devez faire des méthodes séparées pour les types de référence et pour les types primitifs.

+0

Je peux me tromper mais je pense que techniquement 'int' n'a pas de classe mais quand vous utilisez' int.class' les boîtes de compilateur 'int' à' Integer' –

+3

@ matt-b, int.class est une classe _real_. Vérifiez le javadoc pour Class.isPrimitive. Il a été autour depuis 1.1. Bien avant la boxe. Integer.class! = Int.class. Entier.TYPE == int.class. – nicerobot

+0

"Le type de' int.class' est 'Classe ' "- Je pense que la clé réside ici – polygenelubricants

3

Quelques points:

  1. primitives sont autoboxed à leurs homologues d'objets (emballages) en cas de besoin
  2. tableaux primitifs sont des objets, de sorte qu'ils ne sont pas autoboxed.
  3. génériques ne peuvent pas utiliser les primitives comme paramètres de type

Pour vos exemples, voici mes hypothèses:

dans 3 l'autoboxing arrive au paramètre de type , mais ne se produit pas au tableau retourné
dans 4 le autoboxing arrive au paramètre de type , mais ne se produit pas à l'argument de la méthode , donc en fait un int[] est généré, mais Integer[] est ex pecté

Autoboxing dans le cas de paramètre de type pourrait ne pas être exactement autoboxing, mais quelque chose avec la même idée.

Mise à jour: votre deuxième exemple n'a rien de mal. int.class est un Class, donc le compilateur n'a aucune raison de le rejeter.

+0

Ce sont tous de bons points, mais ils ne répondent pas à ma question. De plus, il n'y a pas d'autoboxing dans mon exemple. Si vous imprimez 'componentType' dans' genericArrayNewInstance', il imprime les objets '.class' passés comme il se doit. – polygenelubricants

+0

@polygenelubricants c'est ce que j'ai dit - autoboxing n'arrive pas à la méthode argumente - il est arrivé (bien, ce n'est peut-être pas exactement autoboxing, mais quelque chose avec une idée similaire) au paramètre type. Le paramètre de type _cannot_ étant primitif, le wrapper est utilisé. – Bozho

+0

"votre deuxième exemple n'a rien de mal" - mais ma spécification de type générique exige que 'T extends Number', où' int.class extends null'. Voir aussi la mise à jour de Q. – polygenelubricants

0

Je suis d'accord avec l'affiche originale. C'est fou. Pourquoi ne puis-je pas utiliser primitive avec générique? Ce n'est peut-être pas un problème de compilation, mais c'est le problème de la langue. Il suffit d'ignorer les types primitifs de génériques.

Pour cela:

intArray = (int []) Array.newInstance (int.class, 0);

int.class est juste un objet Class. Donc c'est ok pour passer. "int" est un type, donc ce n'est pas correct car c'est clairement primitif. Pour ne pas dire que c'est le "meilleur" moyen de créer la langue, juste pour adhérer à la langue.

Ceci est tellement fou que je ne peux pas créer un wrapper pour l'allocation de mémoire (tableau) de primitives en utilisant générique. Si vous utilisez des objets, cela est tellement bouffant pour une énorme collection qui est un gaspillage. Les personnes qui ont créé le langage/la machine Java ont clairement une limite dans leur cerveau. Ils peuvent se tromper la première fois, mais la réparation prend une décennie, et ne pas le faire correctement.

Questions connexes