Это не домашняя работа.
Это возможный записать общий метод, что-то вроде этого:
<T extends Number> T plusOne(T num) {
return num + 1; // DOESN'T COMPILE! How to fix???
}
За исключением использования набора instanceof
и броски, действительно ли это возможно?
Следующие 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
?
FWIW на самом деле это не ограничение дженериков.
Оператор + работает только с примитивами. Причина, по которой он работает для Integer или Long, заключается в автобоксировании / распаковке с их примитивными типами. Не все подклассы Number имеют соответствующий примитивный тип, но, что более важно, Number не имеет подходящего примитивного типа. Таким образом, если полностью исключить из него дженерики, следующий код все равно будет неправильным:
public Number plusOne(Number num) {
return num + 1;
}
Арифметические операции в Java работают только с примитивами. Здесь вы комбинируете обобщения и автоматическую распаковку боксов и т. Д.
Для такого простого случая, как ваш, я предлагаю использовать только примитивы.
Проблема здесь в том, что ваш код должен разгруппировать объект, выполнить операции над примитивом, а затем снова разгруппировать его. Java действительно не может этого сделать, потому что к моменту компиляции кода она уже не знает, что такое тип, поэтому она не знает, как распаковать объект.
Ценность дженериков Java заключается в сохранении безопасности типов, т.е. компилятор знает реальный класс и предотвращает незаконные присваивания. Компилятор НЕ будет генерировать разный код в зависимости от типа: он не скажет: "О, это целое число, поэтому мне нужно сгенерировать целочисленное сложение здесь, в отличие от того, что это строка, поэтому знак плюс на самом деле означает конкатенацию строк". Это действительно сильно отличается от шаблонов C++, если вы об этом думаете.
Единственный способ заставить это работать - это если бы для Number была определена функция plusOne, а ее нет.
Для этого нет удовлетворительного решения, поскольку java.lang.Number
не указывает ничего, что было бы полезно для вычисления преемник числа
.
Вам нужно будет выполнить instanceof
для проверки типов числовых полей и обработать каждый случай отдельно. Также обратите внимание, что вы можете получить instanceof Number
, который не является типом числового поля, например BigInteger
, AtomicLong
и потенциально неизвестные подклассы Number
(например, Rational
и т. Д.).
Взгляд здесь очень обманчивый.Эти 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 метода нельзя обобщить в один метод, поскольку они содержат очень разные инструкции.
Не все подклассы Number можно автоматизировать. Например, BigDecimal нельзя автоматически распаковать. Поэтому оператор "+" для него не работает.
Не самое красивое решение, но если вы полагаетесь на следующие свойства каждой известной реализации Number (в JDK):
Вы можете реализовать его с помощью отражения и использования 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, вы можете гарантировать, что это всегда будет безопасное отливку.
Часть 1:
Разве num + 1
не работает без необходимости создания такого метода? Оператор +
перегружен именно для этого. То есть, зачем звонить:
Integer n = plusOne(anotherInt);
, когда вы можете:
Integer n = anotherInt + 1;
Суть в том, что вы не можете комбинировать автобокс с дженериками.