Pourquoi java.lang.Number ne met-il pas en œuvre Comparable?

Est-ce que quelqu’un sait pourquoi java.lang.Number pas Comparable ? Cela signifie que vous ne pouvez pas sortinger les Number avec Collections.sort ce qui me semble un peu étrange.

Mettre à jour la discussion:

Merci pour toutes les réponses utiles. J’ai fini par faire plus de recherches sur ce sujet .

L’explication la plus simple de la raison pour laquelle java.lang.Number n’est pas implémenté Comparable réside dans les problèmes de mutabilité.

java.lang.Number est le super-type abstrait d’ AtomicInteger , AtomicLong , BigDecimal , BigInteger , Byte , Double , Float , Integer , Long et Short . Sur cette liste, AtomicInteger et AtomicLong ne doivent pas implémenter Comparable .

En fouillant, j’ai découvert que ce n’est pas une bonne pratique d’implémenter des types Comparable , car les objects peuvent changer pendant ou après la comparaison, rendant le résultat de la comparaison inutile. AtomicLong et AtomicInteger sont tous deux AtomicLong . Les concepteurs de l’API avaient la prévoyance de ne pas pouvoir utiliser l’outil Number Comparable car cela aurait contraint l’implémentation des sous-types futurs. En effet, AtomicLong et AtomicInteger ont été ajoutés en Java 1.5 longtemps après l’implémentation de java.lang.Number .

Outre la mutabilité, il y a probablement d’autres considérations à prendre en compte. Une implémentation compareTo dans Number devrait promouvoir toutes les valeurs numériques dans BigDecimal car elle est capable de prendre en charge tous les sous-types Number . L’implication de cette promotion en termes de mathématiques et de performance est un peu floue pour moi, mais mon intuition trouve cette solution difficile.

Il est à noter que l’expression suivante:

 new Long(10).equals(new Integer(10)) 

est toujours false , ce qui a tendance à faire sauter tout le monde à un moment ou à un autre. Ainsi, non seulement vous ne pouvez pas comparer des Number arbitraires mais vous ne pouvez même pas déterminer s’ils sont égaux ou non.

De plus, avec les types primitifs réels ( float , double ), déterminer si deux valeurs sont égales est délicat et doit être fait dans une marge d’erreur acceptable. Essayez le code comme:

 double d1 = 1.0d; double d2 = 0.0d; for (int i=0; i<10; i++) { d2 += 0.1d; } System.out.println(d2 - d1); 

et vous serez laissé avec une petite différence.

Donc, revenons à la question de rendre le Number Comparable . Comment le metsortingez-vous en œuvre? Utiliser quelque chose comme doubleValue() ne le ferait pas de manière fiable. Rappelez-vous que les sous-types Number sont:

  • Byte ;
  • Short ;
  • Integer ;
  • Long ;
  • AtomicInteger ;
  • AtomicLong ;
  • Float
  • Double ;
  • BigInteger ; et
  • BigDecimal .

Pourriez-vous coder une méthode compareTo() fiable qui ne se transforme pas en une série d'instructions if instanceof? Number instances Number ne disposent que de six méthodes:

  • byteValue() ;
  • shortValue() ;
  • intValue() ;
  • longValue() ;
  • floatValue() ; et
  • doubleValue() .

Donc, je suppose que Sun a pris la décision (raisonnable) que les Number ne sont Comparable qu’à eux-mêmes.

Pour la réponse, voir le bogue de bogue de Java 4414323 . Vous pouvez également trouver une discussion à partir de comp.lang.java.programmer

Pour citer la réponse de Sun au rapport de bogue de 2001:

Tous les “numéros” ne sont pas comparables. comparable suppose un classement total des nombres possible. Ce n’est même pas vrai pour les nombres à virgule flottante; NaN (pas un nombre) n’est ni inférieur, supérieur ou égal à aucune valeur à virgule flottante, même lui-même. {Float, Double} .compare impose un classement total différent de celui des opérateurs “< " et "=" à virgule flottante. De plus, comme elles sont actuellement implémentées, les sous-classes de Number ne sont comparables qu’aux autres instances de la même classe. Il existe d'autres cas, tels que des nombres complexes, pour lesquels il n'existe pas de classement total standard, bien qu'il soit possible d'en définir un. En résumé, la question de savoir si une sous-classe de Number est comparable doit être considérée comme une décision pour cette sous-classe.

pour mettre en œuvre un nombre comparable, vous devez écrire du code pour chaque paire de sous-classes. Il est plus facile de simplement autoriser les sous-classes à mettre en œuvre des méthodes comparables.

Très probablement parce qu’il serait plutôt inefficace de comparer les chiffres – la seule représentation à laquelle chaque numéro peut correspondre pour permettre une telle comparaison serait BigDecimal.

Au lieu de cela, les sous-classes non atomiques de Number implémentent Comparable lui-même.

Les atomiques sont mutables et ne peuvent donc pas implémenter une comparaison atomique.

Vous pouvez utiliser Transmorph pour comparer des nombres en utilisant sa classe NumberComparator.

 NumberComparator numberComparator = new NumberComparator(); assertTrue(numberComparator.compare(12, 24) < 0); assertTrue(numberComparator.compare((byte) 12, (long) 24) < 0); assertTrue(numberComparator.compare((byte) 12, 24.0) < 0); assertTrue(numberComparator.compare(25.0, 24.0) > 0); assertTrue(numberComparator.compare((double) 25.0, (float) 24.0) > 0); assertTrue(numberComparator.compare(new BigDecimal(25.0), (float) 24.0) > 0); 

Pour essayer de résoudre le problème d’origine (sortinger une liste de nombres), une option consiste à déclarer la liste d’un type générique d’extension et d’implémenter Comparable.

Quelque chose comme:

 > void processNumbers(List numbers) { System.out.println("Unsorted: " + numbers); Collections.sort(numbers); System.out.println(" Sorted: " + numbers); // ... } void processIntegers() { processNumbers(Arrays.asList(7, 2, 5)); } void processDoubles() { processNumbers(Arrays.asList(7.1, 2.4, 5.2)); } 

Il n’y a pas de comparaison de stardard pour les nombres de types différents. Cependant, vous pouvez écrire votre propre comparateur et l’utiliser pour créer un TreeMap , TreeSet ou Collections.sort (List , Comparator) ou Arrays.sort (Number [], Comparator);

Ecrivez votre propre comparateur

 import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class NumberComparator implements Comparator { @SuppressWarnings("unchecked") @Override public int compare(Number number1, Number number2) { if (((Object) number2).getClass().equals(((Object) number1).getClass())) { // both numbers are instances of the same type! if (number1 instanceof Comparable) { // and they implement the Comparable interface return ((Comparable) number1).compareTo(number2); } } // for all different Number types, let's check there double values if (number1.doubleValue() < number2.doubleValue()) return -1; if (number1.doubleValue() > number2.doubleValue()) return 1; return 0; } /** * DEMO: How to compare apples and oranges. */ public static void main(Ssortingng[] args) { ArrayList listToSort = new ArrayList(); listToSort.add(new Long(10)); listToSort.add(new Integer(1)); listToSort.add(new Short((short) 14)); listToSort.add(new Byte((byte) 10)); listToSort.add(new Long(9)); listToSort.add(new AtomicLong(2)); listToSort.add(new Double(9.5)); listToSort.add(new Double(9.0)); listToSort.add(new Double(8.5)); listToSort.add(new AtomicInteger(2)); listToSort.add(new Long(11)); listToSort.add(new Float(9)); listToSort.add(new BigDecimal(3)); listToSort.add(new BigInteger("12")); listToSort.add(new Long(8)); System.out.println("unsorted: " + listToSort); Collections.sort(listToSort, new NumberComparator()); System.out.println("sorted: " + listToSort); System.out.print("Classes: "); for (Number number : listToSort) { System.out.print(number.getClass().getSimpleName() + ", "); } } } 

pourquoi cela aurait été une mauvaise idée? :

 abstract class ImmutableNumber extends Number implements Comparable { // do NOT implement compareTo method; allowed because class is abstract } class Integer extends ImmutableNumber { // implement compareTo here } class Long extends ImmutableNumber { // implement compareTo here } 

une autre option a peut-être été de déclarer les implémentations de Number Number Comparable, d’omettre l’implémentation compareTo et de l’implémenter dans certaines classes comme Integer, tout en lançant UnsupportedException dans d’autres classes comme AtomicInteger.

J’imagine qu’en ne mettant pas en œuvre Comparable, cela donne plus de souplesse à la mise en œuvre de classes pour l’implémenter ou non. Tous les numéros courants (Integer, Long, Double, etc.) implémentent Comparable. Vous pouvez toujours appeler Collections.sort tant que les éléments implémentent Comparable.

En regardant la hiérarchie des classes. Les classes d’encapsuleur telles que Long, Integer, etc. implémentent Comparable, c’est-à-dire qu’un Integer est comparable à un entier, et qu’un long est comparable à un long, mais que vous ne pouvez pas les mélanger. Au moins avec ce paradigme des génériques. Ce que je suppose répond à votre question «pourquoi».

byte (primitive) est un int (primitive). Les primitives n’ont qu’une valeur à la fois.
Les règles de conception de langue le permettent.

 int i = 255 // down cast primitive (byte) i == -1 

Un Byte n’est pas un Integer . Byte est un Number et un Integer est un Number . Number objects Number peuvent avoir plus d’une valeur à la fois.

 Integer iObject = new Integer(255); System.out.println(iObject.intValue()); // 255 System.out.println(iObject.byteValue()); // -1 

Si un Byte est un Integer et un Integer un Number , quelle valeur utiliserez-vous dans la compareTo(Number number1, Number number2) ?