Сравнение значений двух общих чисел - PullRequest
52 голосов
/ 21 апреля 2010

Я хочу сравнить с переменными типа T extends Number. Теперь я хочу знать, какая из двух переменных больше другой или равна. К сожалению, я еще не знаю точный тип, я только знаю, что это будет подтип java.lang.Number. Как я могу это сделать?

EDIT : я попробовал другой способ, используя TreeSet s, который фактически работал с естественным упорядочением (конечно, все подклассы Number реализуют Comparable, за исключением AtomicInteger и AtomicLong). Таким образом я потеряю дублирующиеся значения. При использовании List s, Collection.sort() не примет мой список из-за связанных несоответствий. Очень неудовлетворительно.

Ответы [ 11 ]

32 голосов
/ 20 марта 2012

Это должно работать для всех классов, которые расширяют Number и являются сопоставимыми для себя. Добавив & Comparable, вы разрешите удалить все проверки типов и бесплатно предоставите проверки типов во время выполнения и выдачу ошибок по сравнению с ответом Sarmun.

class NumberComparator<T extends Number & Comparable> implements Comparator<T> {

    public int compare( T a, T b ) throws ClassCastException {
        return a.compareTo( b );
    }
}
29 голосов
/ 21 апреля 2010

Рабочее (но хрупкое) решение выглядит примерно так:

class NumberComparator implements Comparator<Number> {

    public int compare(Number a, Number b){
        return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
    }

}

Это все еще не так здорово, поскольку оно рассчитывает на toString, возвращая значение, доступное для BigDecimal (что стандартноКлассы Java Number делают, но этого не требует контракт Number.

Правка, семь лет спустя: Как указано в комментариях, есть (по крайней мере,?) три особых случая toString могут привести к тому, что вам нужно принять во внимание:

13 голосов
/ 07 ноября 2013

Одним из решений, которое может работать для вас, является работа не с 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
}
12 голосов
/ 14 октября 2012

Задав подобный вопрос и изучив ответы здесь, я пришел к следующему. Я думаю, что это более эффективно и надежно, чем решение 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);
    }
}
5 голосов
/ 21 апреля 2010

Самый «универсальный» Java-примитив номер double, поэтому просто используйте

a.doubleValue() > b.doubleValue()

должно быть достаточно в большинстве случаев, но ... здесь есть тонкие проблемы при преобразовании чисел в двойные. Например, с 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% точного способа. В числовом интерфейсе нет метода, подобного sharpValue (), преобразующего в какой-либо тип, способный идеально представить число без потери какой-либо информации.

На самом деле иметь такие совершенные числа вообще невозможно - например, представление числа Pi невозможно, используя любую арифметику с использованием конечного пространства.

2 голосов
/ 15 января 2011

Это должно работать для всех классов, которые расширяют Number и являются сопоставимыми для себя.

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();
    }
}
2 голосов
/ 21 апреля 2010
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 голос
/ 22 апреля 2010

А как насчет этого? Определенно не очень хорошо, но это касается всех упомянутых необходимых случаев.

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!");
            }

        }

    }
1 голос
/ 21 апреля 2010

Вы можете просто использовать метод Number's doubleValue() для их сравнения;однако результаты могут оказаться недостаточно точными для ваших нужд.

0 голосов
/ 21 апреля 2010

Если ваши экземпляры 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 s реализуют Comparable

EDIT

Это дорого из-за размышлений: я знаю

РЕДАКТИРОВАТЬ 2 :

Это, конечно, не относится к случаю, когда вы хотите сравнить десятичные дроби с целыми числами или что-то подобное ...

РЕДАКТИРОВАТЬ 3 :

Предполагается, что нет никаких пользовательских потомков Number, которые не реализуют Comparable (спасибо @DJClayworth)

...