Можно ли написать общий метод +1 для типов числовых блоков в Java? - PullRequest
5 голосов
/ 14 мая 2010

Это НЕ домашняя работа.

Часть 1

Можно ли написать универсальный метод, что-то вроде этого:

<T extends Number> T plusOne(T num) {
    return num + 1; // DOESN'T COMPILE! How to fix???
}

Если не использовать кучу instanceof и приведений, это возможно?


часть 2

Компилируются следующие 3 метода:

Integer plusOne(Integer num) {
    return num + 1;
}   
Double plusOne(Double num) {
    return num + 1;
}
Long plusOne(Long num) {
    return num + 1;
}

Можно ли написать универсальную версию, ограничивающую T только Integer, Double или Long?

Ответы [ 7 ]

6 голосов
/ 14 мая 2010

часть 1

Для этого нет удовлетворительного решения, поскольку java.lang.Number не указывает ничего, что было бы полезно для вычисления преемника Number.

Вы должны будете сделать instanceof проверки для типов числовых ящиков и обработать каждый случай специально. Также обратите внимание, что вы можете получить instanceof Number, который не относится ни к одному из типов числовых полей, например BigInteger, AtomicLong и потенциально неизвестные подклассы Number (например, Rational и т. Д.).

Часть 2

Смотри, здесь очень обманчиво. Эти 3 метода могут выглядеть одинаково, но автобокс / распаковка скрывает тот факт, что они на самом деле сильно отличаются на уровне байт-кода:

Integer plusOne(Integer);
  Code:
   0:   aload_1
   1:   invokevirtual   #84; //int Integer.intValue()
   4:   iconst_1
   5:   iadd
   6:   invokestatic    #20; //Integer Integer.valueOf(int)
   9:   areturn

Double plusOne(Double);
  Code:
   0:   aload_1
   1:   invokevirtual   #91; //double Double.doubleValue()
   4:   dconst_1
   5:   dadd
   6:   invokestatic    #97; //Double Double.valueOf(double)
   9:   areturn

Long plusOne(Long);
  Code:
   0:   aload_1
   1:   invokevirtual   #102; //Long Long.longValue()
   4:   lconst_1
   5:   ladd
   6:   invokestatic    #108; //Long Long.valueOf(long)
   9:   areturn

Мало того, что 3 метода вызывают разные методы xxxValue() и valueOf() для разных типов, но и инструкция для помещения константы 1 в стек также различна (iconst_1, dconst_1 и lconst_1).

Даже если возможно связать универсальный тип, такой как <T=Integer|Long|Double>, эти 3 метода не могут быть обобщены в один метод, поскольку они содержат очень разные инструкции.

5 голосов
/ 14 мая 2010

Не все подклассы Number могут быть автоматически подключены. Например, BigDecimal не может быть автоматически загружен. Поэтому оператор "+" не будет работать для него.

4 голосов
/ 14 мая 2010

Не самое симпатичное решение, но если вы полагаетесь на следующие свойства каждой известной реализации Number (в JDK):

  • Все они могут быть созданы из их представления String с помощью конструктора с одним аргументом
  • Ни у одного из них нет чисел, которые не могут быть представлены BigDecimal

Вы можете реализовать это, используя отражение и используя Generics, чтобы избежать необходимости приводить результат:

public class Test {

    @SuppressWarnings("unchecked")
    public static <T extends Number> T plusOne(T num) {
        try {
            Class<?> c = num.getClass();
            Constructor<?> constr = c.getConstructor(String.class);
            BigDecimal b = new BigDecimal(num.toString());
            b = b.add(java.math.BigDecimal.ONE);
            return (T) constr.newInstance(b.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        System.out.println(plusOne(1));
        System.out.println(plusOne(2.3));
        System.out.println(plusOne(2.4E+120));
        System.out.println(plusOne(2L));
        System.out.println(plusOne(4.5f));
        System.out.println(plusOne(new BigInteger("129481092470147019409174091790")));
        System.out.println(plusOne(new BigDecimal("12948109247014701940917.4091790")));
    }

}

Возвращение выполняется с использованием явно небезопасного преобразования, но, учитывая, что вы используете конструктор класса некоторого T или потомка T, вы можете быть уверены, что это всегда будет безопасное приведение.

2 голосов
/ 14 мая 2010

ПОЭТОМУ это на самом деле не ограничение дженериков.

Оператор + работает только на примитивах. Причина, по которой он работает для Integer или Long, заключается в автобоксировании / распаковке с их примитивными типами. Не все подклассы Number имеют совпадающий тип примитива, но, что более важно, Number не имеет совпадающий тип примитива. Таким образом, полностью исключив дженерики, следующий код все равно будет неправильным:

public Number plusOne(Number num) {
    return num + 1;
}
1 голос
/ 14 мая 2010

Проблема в том, что ваш код должен распаковать объект, работать с примитивом, а затем перезаписать его. Java действительно не может этого сделать, потому что к тому времени, когда код компилируется, он уже не знает, что это за тип, поэтому не знает, как его распаковать.

Значение Java-обобщений действительно заключается в сохранении безопасности типов, т. Е. Компилятор знает реальный класс и предотвращает недопустимые присвоения. Компилятор НЕ будет генерировать различный код в зависимости от типа: он не скажет «о, это целое число, поэтому мне нужно сгенерировать целочисленное добавление здесь, а не String, поэтому знак плюс действительно означает конкатенацию строк». Это действительно сильно отличается от шаблонов C ++, если вы об этом думаете.

Единственный способ, которым вы могли бы выполнить эту работу, был бы, если бы для Number была определена функция plusOne, которой нет.

1 голос
/ 14 мая 2010

Часть 1:

Не работает num + 1 без необходимости создавать такой метод? Оператор + перегружен только для этого. То есть зачем звонить:

Integer n = plusOne(anotherInt);

когда вы можете сделать:

Integer n = anotherInt + 1;

Суть в том, что вы не можете комбинировать автобокс с генериками

0 голосов
/ 14 мая 2010

Арифметические операции в Java работают только на примитивах. Вы здесь комбинируете дженерики, автоматическую коробку, распаковку и т. Д.

Для такого простого случая, как ваш, я предлагаю использовать только примитивы.

...