Действительно ли возможно записать универсальный +1 метод для числовых типов поля в Java?

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

Часть 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?

5
задан Jeff Axelrod 30 March 2012 в 02:42
поделиться

7 ответов

FWIW на самом деле это не ограничение дженериков.

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

public Number plusOne(Number num) {
    return num + 1;
}
2
ответ дан 18 December 2019 в 08:27
поделиться

Арифметические операции в Java работают только с примитивами. Здесь вы комбинируете обобщения и автоматическую распаковку боксов и т. Д.

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

0
ответ дан 18 December 2019 в 08:27
поделиться

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

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

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

1
ответ дан 18 December 2019 в 08:27
поделиться

Часть 1

Для этого нет удовлетворительного решения, поскольку java.lang.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 ).

Даже если можно связать универсальный тип, например , эти 3 метода нельзя обобщить в один метод, поскольку они содержат очень разные инструкции.

6
ответ дан 18 December 2019 в 08:27
поделиться

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

5
ответ дан 18 December 2019 в 08:27
поделиться

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

4
ответ дан 18 December 2019 в 08:27
поделиться

Часть 1:

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

Integer n = plusOne(anotherInt);

, когда вы можете:

Integer n = anotherInt + 1;

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

1
ответ дан 18 December 2019 в 08:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: