2010-04-21 15 views
43

Je veux comparer aux variables, les deux de type T extends Number. Maintenant, je veux savoir laquelle des deux variables est plus grande que l'autre ou égale. Malheureusement je ne connais pas encore le type exact, je sais seulement que ce sera un sous-type de java.lang.Number. Comment puis je faire ça?Comparer les valeurs de deux nombres génériques

EDIT: J'ai essayé une autre solution à l'aide TreeSet s, ce qui en fait travaillé avec ordre naturel (bien sûr cela fonctionne, toutes les sous-classes de Number mettre en œuvre Comparable sauf pour AtomicInteger et AtomicLong). Ainsi, je vais perdre les valeurs en double. Lorsque vous utilisez List s, Collection.sort() n'acceptera pas ma liste en raison de discordances liées. Très insatisfaisant.

+5

Hmm - cela est lié: http://stackoverflow.com/questions/480632/why-doesnt-java-lang-number-implement-comparable – Jonik

+2

Cela ne devrait pas être étiqueté "génériques". Les "nombres génériques" ne se réfèrent pas aux génériques Java dans ce cas. – DJClayworth

+0

Google Collections a un TreeMultiSet qui vous permet de trier les choses sans perdre les doublons. – gustafc

Répondre

28

Un travail (mais fragile) solution est quelque chose comme ceci:

class NumberComparator implements Comparator<Number> { 

    public int compare(Number a, Number b){ 
     return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString())); 
    } 

} 

Il est toujours pas grand, cependant, puisqu'il compte sur toString retournant une analysable de valeur par BigDecimal (que les standards Java Number cours font, mais que le contrat Number ne demande pas).

Edition, sept ans plus tard: Comme indiqué dans les commentaires, il y a trois cas particuliers toString peut produire que vous devez prendre en ce qui concerne (au moins?):

+4

c'est peu probable mais cela peut provoquer NumberFormatException si je crée une sous-classe de Number et que je ne remplace pas toString correctement. – Roman

+1

Espérons que le programmeur saura si des descendants de Number autres que les standards peuvent être rencontrés. – DJClayworth

+0

Zeeks! C'est une très mauvaise solution. Bien que je ne puisse pas penser à un meilleur, je ne vais pas encore baisser les bras. :-( – user949300

2
if(yourNumber instanceof Double) { 
    boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue(); 
    // [...] 
} 

Note: Le chèque instanceof est pas forcément nécessaire - dépend de la façon que vous voulez exactement les comparer. Bien sûr, vous pouvez toujours utiliser .doubleValue(), car chaque numéro doit fournir les méthodes listées here.

Modifier: Comme indiqué dans les commentaires, vous devrez (toujours) vérifier BigDecimal et vos amis. Mais ils fournissent une méthode .compareTo():

if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal) { 
    boolean greaterThanOtherNumber = ((BigDecimal)yourNumber).compareTo((BigDecimal)otherNumber) > 0; 
} 
+1

Qu'en est-il de 'BigDecimal' ou d'autres types hors limites pour les doubles? –

+0

ajoutez simplement un if (votreNumber instanceof BigDecimal && autreNumber instanceof BigDecimal) {boolean greaterThanOtherNumber = votreNumber.compareTo (otherNumber)> 0; } – Tedil

1

Vous pouvez simplement utiliser Number's doubleValue() méthode pour les comparer; Cependant, vous pouvez trouver que les résultats ne sont pas assez précis pour vos besoins.

+3

Je pense que c'est trop risqué – Yaneeve

+1

BigInteger et BigDecimal implémente le nombre aussi. Cela ne marcherait pas. – Roman

+0

Certes, il pourrait être risqué et il pourrait échouer. Il y a plus de mises en garde que le problème de précision/précision que j'ai noté. Mais cela peut convenir aux besoins du PO s'il n'a pas besoin de gérer de gros BigDecimals. –

5

Le plus Java nombre primitif « générique » est double, donc tout simplement en utilisant

a.doubleValue() > b.doubleValue() 

devrait être suffisant dans la plupart des cas, mais ... il y a des problèmes subtils ici lors de la conversion des numéros en double. Par exemple, le suivant est possible avec BigInteger:

BigInteger a = new BigInteger("9999999999999992"); 
    BigInteger b = new BigInteger("9999999999999991"); 
    System.out.println(a.doubleValue() > b.doubleValue()); 
    System.out.println(a.doubleValue() == b.doubleValue()); 

résultats dans:

false 
true 

Bien que je pense que ce soit le cas très extrême cela est possible. Et non - il n'y a pas de manière générique 100% précise. L'interface de nombre n'a aucune méthode comme exactValue() convertissant à un certain type capable de représenter le nombre de façon parfaite sans perdre aucune information.

En réalité, avoir de tels nombres parfaits est impossible en général - par exemple, représenter le nombre Pi est impossible en utilisant une quelconque arithmétique en utilisant un espace fini.

+2

Notez que cela ne fonctionne pas non plus pour Long car ils ne peuvent pas être entièrement représentés en utilisant Double. –

0

Supposons que vous avez une méthode comme:

public <T extends Number> T max (T a, T b) { 
    ... 
    //return maximum of a and b 
} 

Si vous savez qu'il ya des nombres entiers, longs et doubles peuvent être passés en tant que paramètres, vous pouvez modifier la signature de méthode:

public <T extends Number> T max(double a, double b) { 
    return (T)Math.max (a, b); 
} 

Cela fonctionnera pour byte, short, integer, long et double.

Si vous supposez que BigInteger ou BigDecimal ou une combinaison de flottants et de doubles peut être transmise, vous ne pouvez pas créer une méthode commune pour comparer tous ces types de paramètres.

0

Si vos instances numériques sont jamais atomique (c.-à-AtomicInteger), vous pouvez faire quelque chose comme:

private Integer compare(Number n1, Number n2) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { 

Class<? extends Number> n1Class = n1.getClass(); 
if (n1Class.isInstance(n2)) { 
    Method compareTo = n1Class.getMethod("compareTo", n1Class); 
    return (Integer) compareTo.invoke(n1, n2); 
} 

return -23; 
} 

Ceci est depuis non-atomique Number de mettre en œuvre Comparable

EDIT:

Ceci est coûteux du e à la réflexion: Je sais

EDIT 2:

Bien sûr, ne prend pas d'un cas où vous voulez comparer les décimales à ints ou quelque ...

EDIT 3:

Cela suppose qu'il n'y a pas de descendants défini sur mesure Nombre qui ne met pas en œuvre comparable (grâce @DJClayworth)

+2

Cela suppose qu'il n'y a pas de descendants de nombre personnalisé. – DJClayworth

+0

@DJClayworth: vrai, et ce ne sont pas les seules contraintes que je avait des hommes mentionné ci-dessus. Mais pourquoi y aurait-il des 'descendants de nombre' personnalisés? – Yaneeve

+0

Un exemple d'un descendant personnalisé de 'Number' est' org.apache.commons.math.fraction.BigFraction', que j'utilise dans beaucoup de mes programmes. – finnw

2

Qu'est-ce que à propos de celui-ci? Certainement pas gentil, mais il traite de tous les cas nécessaires mentionnés.

public class SimpleNumberComparator implements Comparator<Number> 
    { 
     @Override 
     public int compare(Number o1, Number o2) 
     { 
      if(o1 instanceof Short && o2 instanceof Short) 
      { 
       return ((Short) o1).compareTo((Short) o2); 
      } 
      else if(o1 instanceof Long && o2 instanceof Long) 
      { 
       return ((Long) o1).compareTo((Long) o2); 
      } 
      else if(o1 instanceof Integer && o2 instanceof Integer) 
      { 
       return ((Integer) o1).compareTo((Integer) o2); 
      } 
      else if(o1 instanceof Float && o2 instanceof Float) 
      { 
       return ((Float) o1).compareTo((Float) o2); 
      } 
      else if(o1 instanceof Double && o2 instanceof Double) 
      { 
       return ((Double) o1).compareTo((Double) o2); 
      } 
      else if(o1 instanceof Byte && o2 instanceof Byte) 
      { 
       return ((Byte) o1).compareTo((Byte) o2); 
      } 
      else if(o1 instanceof BigInteger && o2 instanceof BigInteger) 
      { 
       return ((BigInteger) o1).compareTo((BigInteger) o2); 
      } 
      else if(o1 instanceof BigDecimal && o2 instanceof BigDecimal) 
      { 
       return ((BigDecimal) o1).compareTo((BigDecimal) o2); 
      } 
      else 
      { 
       throw new RuntimeException("Ooopps!"); 
      } 

     } 

    } 
+7

Cela ne fonctionnera pas sur une liste de types de nombres mélangés – finnw

1

Ceci devrait fonctionner pour toutes les classes qui étendent Number, et sont comparables à elles-mêmes.

class NumberComparator<T extends Number> implements Comparator<T> { 

    public int compare(T a, T b){ 
     if (a instanceof Comparable) 
      if (a.getClass().equals(b.getClass())) 
       return ((Comparable<T>)a).compareTo(b);   
     throw new UnsupportedOperationException(); 
    } 
} 
24

Cela devrait fonctionner pour toutes les classes qui se prolongent Nombre et sont comparables à eux-mêmes. En ajoutant le & Comparable vous permettez de supprimer toutes les vérifications de type et fournit des vérifications de type à l'exécution et le lancement d'erreur gratuitement par rapport à la réponse de Sarmun.

class NumberComparator<T extends Number & Comparable> implements Comparator<T> { 

    public int compare(T a, T b) throws ClassCastException { 
     return a.compareTo(b); 
    } 
} 
+0

Y at-il un inconvénient avec cette réponse? Sinon, il devrait probablement être choisi comme meilleure réponse. – Campa

+2

Il requiert que la collection source soit un type spécifique, donc elle ne fonctionnera pas pour la classe Number abstraite. – RoninCoder

11

Après avoir demandé un similar question et étudier les réponses ici, je suis venu avec ce qui suit.Je pense qu'il est plus efficace et plus robuste que la solution donnée par gustafc:

public int compare(Number x, Number y) { 
    if(isSpecial(x) || isSpecial(y)) 
     return Double.compare(x.doubleValue(), y.doubleValue()); 
    else 
     return toBigDecimal(x).compareTo(toBigDecimal(y)); 
} 

private static boolean isSpecial(Number x) { 
    boolean specialDouble = x instanceof Double 
      && (Double.isNaN((Double) x) || Double.isInfinite((Double) x)); 
    boolean specialFloat = x instanceof Float 
      && (Float.isNaN((Float) x) || Float.isInfinite((Float) x)); 
    return specialDouble || specialFloat; 
} 

private static BigDecimal toBigDecimal(Number number) { 
    if(number instanceof BigDecimal) 
     return (BigDecimal) number; 
    if(number instanceof BigInteger) 
     return new BigDecimal((BigInteger) number); 
    if(number instanceof Byte || number instanceof Short 
      || number instanceof Integer || number instanceof Long) 
     return new BigDecimal(number.longValue()); 
    if(number instanceof Float || number instanceof Double) 
     return new BigDecimal(number.doubleValue()); 

    try { 
     return new BigDecimal(number.toString()); 
    } catch(final NumberFormatException e) { 
     throw new RuntimeException("The given number (\"" + number + "\" of class " + number.getClass().getName() + ") does not have a parsable string representation", e); 
    } 
} 
+0

Notez que BigDecimal.valueOf doit être utilisé à la place du constructeur ESPECIALLY pour la création Float/Double, sinon vous obtiendrez des erreurs en virgule flottante base2 dans la conversion. – Eric

+0

Merci pour votre commentaire! Je ne suis pas sûr que votre point soit valide cependant. Le Javadoc du double constructeur dit: "[...], notez que ce constructeur fournit une conversion exacte, il ne donne pas le même résultat que la conversion du double en String en utilisant la méthode' Double.toString (double) 'et puis en utilisant le constructeur 'BigDecimal (String)'."Il est vrai que la conversion exacte donne souvent des résultats inattendus (par exemple pour les littéraux' 0.1') .Mais puisque nous ne connaissons rien à l'origine des nombres, je pense que la seule façon raisonnable de les gérer est de ne rien présumer. . – rolve

8

Une solution qui pourrait fonctionner pour vous est de ne pas travailler avec T extends Number mais avec T extends Number & Comparable. Ce type signifie: "T ne peut être mis à des types qui implémente les deux les interfaces."

Cela vous permet d'écrire du code qui fonctionne avec tous les nombres comparables. Typiquement typé et élégant.

C'est la même solution que BennyBoy propose, mais elle fonctionne avec toutes sortes de méthodes, pas seulement des comparateurs.

public static <T extends Number & Comparable<T>> void compfunc(T n1, T n2) { 
    if (n1.compareTo(n2) > 0) System.out.println("n1 is bigger"); 
} 

public void test() { 
    compfunc(2, 1); // Works with Integer. 
    compfunc(2.0, 1.0); // And all other types that are subtypes of both Number and Comparable. 
    compfunc(2, 1.0); // Compilation error! Different types. 
    compfunc(new AtomicInteger(1), new AtomicInteger(2)); // Compilation error! Not subtype of Comparable 
}