Perché java.lang.Number non implementa Comparable?[duplicare]
-
20-08-2019 - |
Domanda
Questa domanda ha già una risposta qui:
- Confronto dei valori di due numeri generici 12 risposte
Qualcuno sa perché java.lang.Number
non implementa Comparable
?Ciò significa che non è possibile ordinare Number
è con Collections.sort
il che mi sembra un po' strano.
Aggiornamento della discussione post:
Grazie per tutte le risposte utiliho finito per fare qualche ulteriore ricerca su questo argomento.
La spiegazione più semplice del motivo per cui java.lang.Number non implementa Comparable è radicata in problemi di mutabilità.
Per un po' di recensione, java.lang.Number
è il supertipo astratto di AtomicInteger
, AtomicLong
, BigDecimal
, BigInteger
, Byte
, Double
, Float
, Integer
, Long
E Short
.In quella lista, AtomicInteger
E AtomicLong
da non implementare Comparable
.
Scavando ho scoperto che non è una buona pratica da attuare Comparable
su tipi mutabili perché gli oggetti possono cambiare durante o dopo il confronto rendendo inutile il risultato del confronto.Entrambi AtomicLong
E AtomicInteger
sono mutevoli.I progettisti dell'API hanno avuto la previdenza di non averlo Number
strumento Comparable
perché avrebbe limitato l'implementazione dei futuri sottotipi.Infatti, AtomicLong
E AtomicInteger
furono aggiunti in Java 1.5 molto tempo dopo java.lang.Number
è stato inizialmente implementato.
A parte la mutevolezza, probabilmente ci sono anche altre considerazioni qui.UN compareTo
implementazione in Number
dovrebbe promuovere tutti i valori numerici a BigDecimal
perché è capace di accogliere tutto Number
sottotipi.Le implicazioni di quella promozione in termini di matematica e prestazioni non mi sono chiare, ma la mia intuizione trova che la soluzione sia goffa.
Soluzione
Vale la pena ricordare che la seguente espressione:
new Long(10).equals(new Integer(10))
è sempre false
, che prima o poi tende a far inciampare tutti.Quindi non solo non puoi confrontare arbitrariamente Number
s ma non puoi nemmeno determinare se sono uguali o no.
Inoltre, con i tipi primitivi reali (float
, double
), determinare se due valori sono uguali è complicato e deve essere effettuato entro un margine di errore accettabile.Prova un codice come:
double d1 = 1.0d;
double d2 = 0.0d;
for (int i=0; i<10; i++) {
d2 += 0.1d;
}
System.out.println(d2 - d1);
e ti rimarrà qualche piccola differenza.
Torniamo quindi alla questione del fare Number
Comparable
.Come lo implementeresti?Usando qualcosa come doubleValue()
non lo farei in modo affidabile.Ricorda il Number
i sottotipi sono:
Byte
;Short
;Integer
;Long
;AtomicInteger
;AtomicLong
;Float
;Double
;BigInteger
;EBigDecimal
.
Potresti codificare un file affidabile compareTo()
metodo che non si trasforma in una serie di istanze if di istruzioni? Number
le istanze hanno a disposizione solo sei metodi:
byteValue()
;shortValue()
;intValue()
;longValue()
;floatValue()
;EdoubleValue()
.
Quindi immagino che Sun abbia preso la (ragionevole) decisione Number
Erano solo Comparable
alle istanze di se stessi.
Altri suggerimenti
Per la risposta, consultare Bugparade Java bug 4414323 . Puoi anche trovare una discussione da comp. lang.java.programmer
Per citare la risposta di Sun alla segnalazione di bug del 2001:
Tutti " numeri " non sono comparabili; comparabile presuppone un ordinamento totale di i numeri sono possibili. Questo non è nemmeno vero per i numeri in virgola mobile; NaN (non un numero) non è né inferiore a, maggiore di, né uguale a nessuno valore in virgola mobile, anche se stesso. {Float, Double} .compare impone un totale ordinamento diverso dall'ordinamento del virgola mobile " < " e " = " operatori. Inoltre, come attualmente implementato, le sottoclassi di Number sono paragonabili solo ad altri casi della stessa classe. Ci sono altri casi, come numeri complessi, dove no esiste un ordinamento totale standard, sebbene uno potrebbe essere definito. Nel in breve, indipendentemente dal fatto che una sottoclasse di Il numero è comparabile dovrebbe essere lasciato come una decisione per quella sottoclasse.
per implementare un numero comparabile, dovresti scrivere il codice per ogni coppia di sottoclassi. È invece più semplice consentire a sottoclassi di implementare comparabili.
Molto probabilmente perché sarebbe piuttosto inefficiente confrontare i numeri - l'unica rappresentazione in cui ogni numero può adattarsi per consentire tale confronto sarebbe BigDecimal.
Invece, le sottoclassi non atomiche di Number implementano lo stesso Comparable.
Quelli atomici sono mutabili, quindi non possono implementare un confronto atomico.
Puoi usare Transmorph per confrontare i numeri usando la sua 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);
Per provare a risolvere il problema originale (ordinare un elenco di numeri), un'opzione è dichiarare l'elenco di un tipo generico estendendo Numero e implementando Comparable.
Qualcosa del tipo:
<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));
}
non esiste un confronto stardard per numeri di tipi diversi. Tuttavia puoi scrivere il tuo comparatore e usarlo per creare una TreeMap & Lt; Number, Object & Gt ;, TreeSet & Lt; Number & Gt; oppure Collections.sort (Elenco < Numero > ;, Comparatore) o Arrays.sort (Numero [], Comparatore);
Scrivi il tuo comparatore
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() + ", ");
}
}
}
perché questa sarebbe stata una cattiva idea? :
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
}
Un'altra opzione potrebbe essere stata quella di dichiarare la classe Numero implementa Comparable, omettere compare Per l'implementazione e implementarla in alcune classi come Integer mentre lancia UnsupportedException in altre come AtomicInteger.
La mia ipotesi è che non implementando Comparable, offre maggiore flessibilità all'implementazione delle classi per implementarlo o meno. Tutti i numeri comuni (intero, lungo, doppio, ecc.) Implementano Comparable. Puoi ancora chiamare Collections.sort finché gli elementi stessi implementano Comparable.
Guardando la gerarchia di classi. Classi wrapper come Long, Integer, ecc., Implementano Comparable, ovvero un Integer è paragonabile a un intero e un long è paragonabile a un long, ma non puoi mescolarli. Almeno con questo paradigma dei generici. Il che immagino risponda alla tua domanda "perché".
byte
(primitivo) è un int
(primitivo). I primitivi hanno un solo valore alla volta.
Le regole di progettazione del linguaggio lo consentono.
int i = 255
// down cast primitive
(byte) i == -1
Un Byte
non è un Integer
. Number
è un compareTo(Number number1, Number number2)
e un <=> è un <=>. <=> gli oggetti possono avere più di un valore contemporaneamente.
Integer iObject = new Integer(255);
System.out.println(iObject.intValue()); // 255
System.out.println(iObject.byteValue()); // -1
Se un <=> è un <=> e un <=> è un <=>, quale valore utilizzerai nel metodo <=>?