web-dev-qa-db-fra.com

Pourquoi Java.lang.Number n'implémente-t-il pas Comparable?

Quelqu'un sait-il pourquoi Java.lang.Number n'implémente pas Comparable? Cela signifie que vous ne pouvez pas trier Numbers avec Collections.sort ce qui me semble un peu étrange.

Mise à jour de la discussion:

Merci pour toutes les réponses utiles. J'ai fini par faire quelques recherches supplémentaires sur ce sujet .

L'explication la plus simple pour laquelle Java.lang.Number n'implémente pas Comparable est enracinée dans des problèmes de mutabilité.

Pour un petit examen, Java.lang.Number est le super-type abstrait de AtomicInteger, AtomicLong, BigDecimal, BigInteger, Byte, Double , Float, Integer, Long et Short. Sur cette liste, AtomicInteger et AtomicLong pour ne pas implémenter Comparable.

En fouillant, j'ai découvert que ce n'est pas une bonne pratique d'implémenter Comparable sur des types mutables car les objets peuvent changer pendant ou après la comparaison, rendant le résultat de la comparaison inutile. AtomicLong et AtomicInteger sont mutables. Les concepteurs d'API avaient la prévoyance de ne pas avoir Number implémenter Comparable car cela aurait limité l'implémentation des futurs sous-types. En effet, AtomicLong et AtomicInteger ont été ajoutés dans Java 1.5 longtemps après Java.lang.Number a été initialement implémenté.

Outre la mutabilité, il y a probablement d'autres considérations ici aussi. Une implémentation de compareTo dans Number devrait promouvoir toutes les valeurs numériques en 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 performances n'est pas claire pour moi, mais mon intuition trouve cette solution délicate.

131
Julien Chastang

Il convient de mentionner que l'expression suivante:

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

est toujours false, ce qui a tendance à déclencher 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 vrais types primitifs (float, double), déterminer si deux valeurs sont égales est délicat et doit être fait dans une marge d'erreur acceptable. Essayez du 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 vous retrouverez avec une petite différence.

Revenons donc à la question de faire NumberComparable. Comment le mettriez-vous en œuvre? Utiliser quelque chose comme doubleValue() ne le ferait pas de manière fiable. N'oubliez pas 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 les instances 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 Numbers n'étaient que Comparable pour leurs propres instances.

63
cletus

Pour la réponse, voir Java bugparade bug 441432 . Vous pouvez également trouver une discussion sur comp.lang.Java.programmer

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

Tous les "nombres" ne sont pas comparables; comparable suppose qu'un classement total des numéros est possible. Ce n'est même pas vrai pour les nombres à virgule flottante; NaN (pas un nombre) n'est ni inférieur, ni supérieur, ni égal à toute valeur à virgule flottante, même lui-même. {Float, Double} .compare impose un ordre total différent de celui des opérateurs à virgule flottante "<" et "=". De plus, telles qu'elles sont actuellement implémentées, les sous-classes de Number ne sont comparables qu'à d'autres instances de la même classe. Il existe d'autres cas, comme les nombres complexes, où aucun ordre total standard n'existe, bien qu'un puisse être défini. En bref, la question de savoir si une sous-classe de Nombre est comparable doit être laissée à la décision de cette sous-classe.

42
Eddie

afin d'implémenter un nombre comparable, vous devez écrire du code pour chaque paire de sous-classes. Il est plus facile de permettre simplement à des sous-classes d'implémenter des éléments comparables.

4
z -

Très probablement parce qu'il serait plutôt inefficace de comparer des nombres - la seule représentation dans laquelle chaque nombre peut s'adapter 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, donc ne peuvent pas implémenter une comparaison atomique.

3
Pete Kirkham

Pour essayer de résoudre le problème d'origine (trier une liste de nombres), une option consiste à déclarer la liste d'un type générique étendant Number et implémentant Comparable.

Quelque chose comme:

<N extends Number & Comparable<N>> void processNumbers(List<N> 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));
}
3
Jaime Saiz

Vous pouvez utiliser Transmorph pour comparer les 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);
3
cchabanois

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 peut avoir été de déclarer la classe Number implémente 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 comme AtomicInteger.

1
Raigedas

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

1
Peter Lawrey

Écrivez 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(String[] 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() + ", ");
 }
    }
}
1
Hilmar

byte (primitif) est un int (primitif). Les primitives n'ont qu'une seule valeur à la fois.
Les règles de conception du langage 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 les objets peuvent avoir plusieurs valeurs en même temps.

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 est un Number, quelle valeur utiliserez-vous dans la méthode compareTo(Number number1, Number number2)?

0
l_39217_l

Je suppose qu'en n'implémentant pas Comparable, cela donne plus de flexibilité à l'implémentation de classes pour l'implémenter ou non. Tous les nombres communs (entier, long, double, etc.) implémentent Comparable. Vous pouvez toujours appeler Collections.sort tant que les éléments eux-mêmes implémentent Comparable.

0
Steve Kuo

En regardant la hiérarchie des classes. Les classes wrapper comme Long, Integer, etc., implémentent Comparable, c'est-à-dire qu'un Integer est comparable à un entier, et un long est comparable à un long, mais vous ne pouvez pas les mélanger. Au moins avec ce paradigme générique. Ce qui, je suppose, répond à votre question "pourquoi".

0
airportyh