Je veux comparer avec des variables, toutes deux de type T extends Number
. Maintenant, je veux savoir laquelle des deux variables est supérieure à 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?
EDIT : J'ai essayé une autre solution de contournement en utilisant TreeSet
s, qui fonctionnait en fait avec un ordre naturel (bien sûr, cela fonctionne, toutes les sous-classes de Number
implémente Comparable
sauf pour AtomicInteger et AtomicLong). Ainsi, je perdrai des valeurs en double. Lorsque vous utilisez List
s, Collection.sort()
n'acceptera pas ma liste en raison de disparités liées. Très insatisfaisant.
Une solution fonctionnelle (mais fragile) ressemble à ceci:
class NumberComparator implements Comparator<Number> {
public int compare(Number a, Number b){
return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
}
}
Ce n'est toujours pas génial, car il compte sur toString
renvoyant une valeur analysable par BigDecimal
(ce que font les classes standard Java Number
) , mais que le contrat Number
n'exige pas).
Edit, sept ans plus tard: Comme indiqué dans les commentaires, il y a (au moins?) Trois cas particuliers toString
peuvent produire cela vous devez prendre en compte:
Infinity
, qui est supérieur à tout, sauf lui-même auquel il est égal-Infinity
, qui est moins que tout, sauf lui-même auquel il est égalNaN
, qui est extrêmement velu/impossible à comparer depuis toutes les comparaisons avec NaN
aboutissent à false
, y compris la vérification de l'égalité avec lui-même .Cela devrait fonctionner pour toutes les classes qui étendent Number et sont comparables à elles-mêmes. En ajoutant le & Comparable, vous autorisez à supprimer toutes les vérifications de type et fournissez gratuitement des vérifications de type à l'exécution et le lancement d'erreurs par rapport à la réponse Sarmun.
class NumberComparator<T extends Number & Comparable> implements Comparator<T> {
public int compare( T a, T b ) throws ClassCastException {
return a.compareTo( b );
}
}
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 défini que sur des types qui implémentent both les interfaces."
Cela vous permet d'écrire du code qui fonctionne avec tous les nombres comparables. Statique et élégant.
C'est la même solution que BennyBoy propose, mais elle fonctionne avec toutes sortes de méthodes, pas seulement avec les classes de comparaison.
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
}
Après avoir posé une question similaire et étudié les réponses ici, j'ai trouvé ce qui suit. Je pense qu'elle est plus efficace et plus robuste que la solution proposé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);
}
}
Le plus "générique" Java nombre primitif est double, donc en utilisant simplement
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 nombres en double. Par exemple, ce qui suit 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ésulte en:
false
true
Bien que je m'attende à ce que ce soit un cas très extrême, cela est possible. Et non - il n'y a aucun moyen générique précis à 100%. L'interface numérique n'a pas de méthode comme exactValue () pour convertir en un type capable de représenter le nombre de manière parfaite sans perdre aucune information.
En fait, avoir de tels nombres parfaits est impossible en général - par exemple, représenter le nombre Pi est impossible en utilisant n'importe quelle arithmétique utilisant un espace fini.
Cela 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();
}
}
if(yourNumber instanceof Double) {
boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue();
// [...]
}
Remarque: La vérification instanceof
n'est pas nécessairement nécessaire - dépend de la façon exacte dont vous souhaitez les comparer. Vous pouvez bien sûr toujours simplement utiliser .doubleValue()
, car chaque Number doit fournir les méthodes listées ici .
Edit : Comme indiqué dans les commentaires, vous devrez (toujours) vérifier BigDecimal et ses amis. Mais ils fournissent une méthode .compareTo()
:
if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal) {
boolean greaterThanOtherNumber = ((BigDecimal)yourNumber).compareTo((BigDecimal)otherNumber) > 0;
}
Vous pouvez simplement utiliser la méthode Number's doubleValue()
pour les comparer; cependant, vous pouvez trouver que les résultats ne sont pas assez précis pour vos besoins.
Et celui-ci? Certainement pas Nice, 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!");
}
}
}
System.out.println(new BigDecimal(0.1d).toPlainString());
System.out.println(BigDecimal.valueOf(0.1d).toPlainString());
System.out.println(BigDecimal.valueOf(0.1f).toPlainString());
System.out.println(Float.valueOf(0.1f).toString());
System.out.println(Float.valueOf(0.1f).doubleValue());
Si vos instances de Number sont ( jamais Atomic (ie 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;
}
En effet, tous les Number
non atomiques implémentent Comparable
MODIFIER :
Cela coûte cher à cause de la réflexion: je sais
EDIT 2:
Bien sûr, cela ne prend pas un cas dans lequel vous souhaitez comparer les décimales aux pouces ou à certains autres ...
MODIFIER:
Cela suppose qu'aucun descendant de Number personnalisé ne met en œuvre Comparable (merci @DJClayworth)
Supposons que vous ayez 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 n'y a que des entiers, des longs et des doubles peuvent être passés en tant que paramètres, vous pouvez changer la signature de la méthode en:
public <T extends Number> T max(double a, double b) {
return (T)Math.max (a, b);
}
Cela fonctionnera pour octet, court, entier, long et double.
Si vous supposez que BigInteger ou BigDecimal ou un mélange de flottants et de doubles peuvent être passés, vous ne pouvez pas créer une méthode commune pour comparer tous ces types de paramètres.