Сравнивая значения двух общих чисел

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, которые могут возникнуть, что вы должны учитывать:


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 или сочетание поплавков и удвоений могут быть переданы, вы не можете создать один общий метод для сравнения всех этих типов параметров.