swagger2 - java versions
¿Por qué java.lang.Number no implementa Comparable? (12)
Esta pregunta ya tiene una respuesta aquí:
- Comparando los valores de dos números genéricos 11 respuestas
¿Alguien sabe por qué java.lang.Number
no implementa Comparable
? Esto significa que no puede ordenar Number
s con Collections.sort
cual me parece un poco extraño.
Publicar discusión de actualización:
Gracias por todas las respuestas útiles. Terminé investigando un poco más sobre este tema .
La explicación más simple de por qué java.lang.Number no implementa Comparable está arraigada en problemas de mutabilidad.
Para una revisión, java.lang.Number
es el super-tipo abstracto de AtomicInteger
, AtomicLong
, BigDecimal
, BigInteger
, Byte
, Double
, Float
, Integer
, Long
y Short
. En esa lista, AtomicInteger
y AtomicLong
no implementan Comparable
.
Excavando, descubrí que no es una buena práctica implementar Comparable
en tipos mutables porque los objetos pueden cambiar durante o después de la comparación, inutilizando el resultado de la comparación. AtomicLong
y AtomicInteger
son mutables. Los diseñadores de la API tenían la previsión de que el Number
implementara Comparable
porque habría restringido la implementación de subtipos futuros. De hecho, AtomicLong
y AtomicInteger
se agregaron en Java 1.5 mucho después de que java.lang.Number
se implementara inicialmente.
Además de la mutabilidad, probablemente haya otras consideraciones aquí también. Una implementación de compareTo
en Number
debería promocionar todos los valores numéricos a BigDecimal
porque es capaz de acomodar todos los subtipos de Number
. La implicación de esa promoción en términos de matemática y rendimiento es un poco confusa para mí, pero mi intuición encuentra que la solución es kludgy.
¿Por qué esto habría sido una mala 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
}
Otra opción puede ser declarar que el Número de clase implementa Comparable, omitir la implementación compareTo e implementarlo en algunas clases como Integer, mientras que lanza UnsupportedException en otras como AtomicInteger.
Escribe tu propio comparador
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() + ", ");
}
}
}
Mi suposición es que al no implementar Comparable, da más flexibilidad para implementar clases para implementarlo o no. Todos los números comunes (Integer, Long, Double, etc.) implementan Comparable. Aún puede llamar a Collections.sort siempre que los elementos implementen Comparable.
Mirando la jerarquía de clases. Las clases Wrapper como Long, Integer, etc. implementan Comparable, es decir, un entero es comparable a un entero, y un largo es comparable a un largo, pero no se pueden mezclar. Al menos con este paradigma de genéricos. Lo cual supongo que responde a tu pregunta ''por qué''.
Muy probablemente porque sería bastante ineficiente comparar números: la única representación en la que puede caber cada número para permitir tal comparación sería BigDecimal.
En cambio, las subclases no atómicas de Número implementan Comparable.
Los atómicos son mutables, por lo que no pueden implementar una comparación atómica.
Para implementar un número comparable, debería escribir código para cada par de subclase. Es más fácil en lugar de permitir que las subclases implementen de forma comparable.
Para la respuesta, vea el error bugparade de Java 4414323 . También puede encontrar una discusión desde comp.lang.java.programmer
Para citar la respuesta de Sun al informe de errores de 2001:
Todos los "números" no son comparables; comparable supone que es posible un ordenamiento total de los números. Esto ni siquiera es verdad para los números de coma flotante; NaN (no es un número) no es menor que, mayor que, ni igual a ningún valor de coma flotante, incluso a sí mismo. {Float, Double} .compare impone un orden total diferente del orden de los operadores de coma flotante "<" y "=". Además, como se implementa actualmente, las subclases de Número solo son comparables con otras instancias de la misma clase. Hay otros casos, como números complejos, en los que no existe un pedido total estándar, aunque se podría definir uno. En resumen, si una subclase de Número es comparable o no se debe dejar como una decisión para esa subclase.
Para tratar de resolver el problema original (ordenar una lista de números), una opción es declarar la lista de un tipo genérico que se extiende Número e implementar Comparable.
Algo como:
<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));
}
Puede usar Transmorph para comparar números usando su clase 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);
Vale la pena mencionar que la siguiente expresión:
new Long(10).equals(new Integer(10))
siempre es false
, lo que tiende a hacer tropezar a todos en algún momento u otro. Entonces no solo no puedes comparar Number
arbitrarios, sino que ni siquiera puedes determinar si son iguales o no.
Además, con los tipos primitivos reales ( float
, double
), determinar si dos valores son iguales es complicado y debe hacerse dentro de un margen de error aceptable. Pruebe el código como:
double d1 = 1.0d;
double d2 = 0.0d;
for (int i=0; i<10; i++) {
d2 += 0.1d;
}
System.out.println(d2 - d1);
y te quedarás con una pequeña diferencia.
Así que volvamos al tema de hacer que el Number
Comparable
. ¿Cómo lo implementarías? Usar algo como doubleValue()
no lo haría de manera confiable. Recuerde que los subtipos de Number
son:
-
Byte
; -
Short
; -
Integer
; -
Long
; -
AtomicInteger
; -
AtomicLong
; -
Float
; -
Double
; -
BigInteger
; y -
BigDecimal
.
¿Podría codificar un compareTo()
fiable compareTo()
que no se transfiera a una serie de sentencias if instance of? Number
instancias Number
solo tienen seis métodos disponibles:
-
byteValue()
; -
shortValue()
; -
intValue()
; -
longValue()
; -
floatValue()
; y -
doubleValue()
Así que supongo que Sun tomó la decisión (razonable) de que los Number
eran solo Comparable
a las instancias de ellos mismos.
no hay comparación estándar para números de diferentes tipos. Sin embargo, puede escribir su propio Comparador y usarlo para crear un TreeMap <Number, Object>, TreeSet <Number> o Collections.sort (Lista <Número>, Comparador) o Arrays.sort (Número [], Comparador);
byte
(primitivo) es un int
(primitivo). Los primitivos tienen solo un valor a la vez.
Las reglas de diseño del lenguaje lo permiten.
int i = 255
// down cast primitive
(byte) i == -1
Un Byte
no es un Integer
. Byte
es un Number
y un Integer
es un Number
. Number
objetos Number
pueden tener más de un valor al mismo tiempo.
Integer iObject = new Integer(255);
System.out.println(iObject.intValue()); // 255
System.out.println(iObject.byteValue()); // -1
Si un Byte
es un Integer
y un Integer
es un Number
, ¿qué valor usará en el compareTo(Number number1, Number number2)
?