Сравнивая значения двух общих чисел
41 b_erb [2010-04-21 16:19:00]
Я хочу сравнить с переменными, как типа T extends Number
. Теперь я хочу знать, какая из двух переменных больше другой или равна. К сожалению, я еще не знаю точного типа, я знаю только, что это будет подтип java.lang.Number
. Как я могу это сделать?
РЕДАКТИРОВАТЬ: я попробовал другое обходное решение, используя TreeSet
s, который фактически работал с естественным упорядочением (конечно, он работает, все подклассы Number
реализуют Comparable
, за исключением AtomicInteger и AtomicLong), Таким образом, я потеряю повторяющиеся значения. При использовании List
s Collection.sort()
не будет принимать мой список из-за связанных несоответствий. Очень неудовлетворительно.
java generics numbers comparable
11 ответов
27 Решение gustafc [2010-04-21 16:40:00]
Рабочее (но хрупкое) решение выглядит примерно так:
class NumberComparator implements Comparator<Number> {
public int compare(Number a, Number b){
return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
}
}
Это все еще не очень хорошо, так как он рассчитывает на toString
, возвращающий значение parsable на BigDecimal
(которое выполняет стандартный класс Java Number
, но который не требует контракта Number
).
Редактировать, семь лет спустя: Как указано в комментариях, есть (по крайней мере?) три специальных случая toString
, которые могут возникнуть, что вы должны учитывать:
-
Infinity
, который больше, чем все, кроме самого, к которому он равен. -
-Infinity
, что меньше всего, кроме самого, к которому оно равно -
NaN
, который чрезвычайно волосатый/невозможно сравнивать, поскольку все сравнения сNaN
приводят кfalse
, включая проверку равенства с самим собой.
23 BennyBoy [2012-03-20 16:50:00]
Это должно работать для всех классов, которые расширяют число и сравнимы с самим собой. Добавляя и сравнивая, вы разрешаете удалять все проверки типов и предоставляете проверки типа времени выполнения и бросание ошибок бесплатно по сравнению с ответом Сармуна.
class NumberComparator<T extends Number & Comparable> implements Comparator<T> {
public int compare( T a, T b ) throws ClassCastException {
return a.compareTo( b );
}
}
11 rolve [2012-10-14 19:15:00]
После запроса подобного вопроса и изучения ответов здесь я придумал следующее. Я думаю, что он эффективнее и надежнее, чем решение 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);
}
}
7 Lii [2013-11-07 18:09:00]
Одним из решений, которое может работать для вас, является работа не с T extends Number
, а с T extends Number & Comparable
. Этот тип означает: "T
может быть установлен только для типов, реализующих оба интерфейса."
Это позволяет вам писать код, который работает со всеми сопоставимыми номерами. Статически типизированный и элегантный.
Это то же самое решение, которое предлагает BennyBoy, но оно работает со всеми типами методов, а не только с компараторами.
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
}
5 kopper [2010-04-21 16:34:00]
Самый "общий" примитивный номер Java - двойной, поэтому просто
a.doubleValue() > b.doubleValue()
должно быть достаточно в большинстве случаев, но... здесь есть тонкие проблемы при преобразовании чисел в double. Например, с 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());
приводит к:
false
true
Хотя я ожидаю, что это очень экстремальный случай, это возможно. И нет - нет точного 100% -ного точного способа. В числовом интерфейсе нет метода, например exactValue(), который преобразуется в некоторый тип, способный представлять число совершенным образом без потери информации.
На самом деле таких совершенных чисел вообще невозможно - например, число Pi невозможно с использованием любой арифметики с использованием конечного пространства.
2 b_erb [2010-04-22 01:19:00]
Как насчет этого? Определенно не приятно, но речь идет обо всех упомянутых случаях.
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!");
}
}
}
2 Tedil [2010-04-21 16:23:00]
if(yourNumber instanceof Double) {
boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue();
// [...]
}
Примечание. Проверка instanceof
необязательно необходима - зависит от того, как именно вы хотите их сравнить. Конечно, вы всегда можете использовать .doubleValue()
, так как каждый номер должен предоставить перечисленные ниже методы здесь.
Изменить: Как указано в комментариях, вы всегда будете проверять BigDecimal и друзей. Но они предоставляют метод .compareTo()
:
if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal) {
boolean greaterThanOtherNumber = ((BigDecimal)yourNumber).compareTo((BigDecimal)otherNumber) > 0;
}
1 Sarmun [2011-01-15 19:29:00]
Это должно работать для всех классов, которые расширяют число и сравниваются с самими собой.
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();
}
}
1 Steven Mackenzie [2010-04-21 16:25:00]
Вы можете просто использовать метод Number doubleValue()
для их сравнения; однако вы можете обнаружить, что результаты недостаточно точны для ваших нужд.
0 Yaneeve [2010-04-21 16:47:00]
Если ваши экземпляры Number никогда Atomic (т.е. AtomicInteger), вы можете сделать что-то вроде:
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;
}
Это потому, что все неатомные Number
реализуют Comparable
ИЗМЕНИТЬ
Это дорого из-за отражения: я знаю
РЕДАКТИРОВАТЬ 2:
Это, конечно, не принимает случай, в котором вы хотите сравнить десятичные числа с ints или некоторые такие...
РЕДАКТИРОВАТЬ 3:
Это предполагает, что не существует пользовательских потомков Number, которые не реализуют Comparable (спасибо @DJClayworth)
0 Roman [2010-04-21 16:36:00]
Предположим, что у вас есть метод:
public <T extends Number> T max (T a, T b) {
...
//return maximum of a and b
}
Если вы знаете, что в качестве параметров могут быть указаны только целые числа, длинные и удвоенные значения, вы можете сменить подпись метода на:
public <T extends Number> T max(double a, double b) {
return (T)Math.max (a, b);
}
Это будет работать для байтов, коротких, целых, длинных и двойных.
Если вы предполагаете, что BigInteger или BigDecimal или сочетание поплавков и удвоений могут быть переданы, вы не можете создать один общий метод для сравнения всех этих типов параметров.