Я довольно новичок в Java и в программировании.
Тем не менее, в последнее время я столкнулся с такой же ситуацией, когда потребовались значения без знака.
Мне потребовалось около двух недель, чтобы написать все, что я имел в виду, но я полный нуб, так что вы могли бы потратить гораздо меньше.
Общая идея состоит в том, чтобы создать интерфейс, я назвал его: UnsignedNumber<Base, Shifted>
и расширить Number.class, реализуя абстрактный класс AbstractUnsigned<Base, Shifted, Impl extends AbstractUnsigned<Base, Shifted, Impl>>
.
Таким образом, Базовый параметризованный тип представляет базовый тип, Shifted представляет фактический тип Java. Impl является ярлыком для реализации этого абстрактного класса.
Большую часть времени потребляет шаблон Java 8 Lambdas и внутренние частные классы и процедуры безопасности. Важным было добиться поведения без знака, когда математическая операция, такая как вычитание или сложение с отрицательным значением, порождает нулевой предел: переполняет верхний предел со знаком назад.
Наконец, потребовалось еще несколько дней, чтобы кодировать фабрики и подклассы реализации.
Пока я знаю:
UByte и MUByte
UShort и MUShort
UInt и MUInt
... и т. Д.
Они являются потомками AbstractUnsigned:
UByte или MUByte extension AbstractUnsigned<Byte, Short, UByte>
или AbstractUnsigned<Byte, Short, MUByte>
UShort или MUShort extension AbstractUnsigned<Short, Integer, UShort>
или AbstractUnsigned<Short, Integer, MUShort>
... и т.д.
Общая идея состоит в том, чтобы принять верхний предел без знака как смещенный (приведенный) тип и транспонирование кода отрицательных значений, поскольку они должны были прийти не от нуля, а без верхнего знака.
UPDATE:
(Благодаря Ajeans добрым и вежливым указаниям)
/**
* Adds value to the current number and returns either
* new or this {@linkplain UnsignedNumber} instance based on
* {@linkplain #isImmutable()}
*
* @param value value to add to the current value
* @return new or same instance
* @see #isImmutable()
*/
public Impl plus(N value) {
return updater(number.plus(convert(value)));
}
Это доступный извне метод AbstractUnsigned<N, Shifted, Impl>
(или, как было сказано ранее AbstractUnsigned<Base, Shifted, Impl>
);
Теперь, чтобы работать под капотом:
private Impl updater(Shifted invalidated){
if(mutable){
number.setShifted(invalidated);
return caster.apply(this);
} else {
return shiftedConstructor.apply(invalidated);
}
}
В приведенном выше частном методе mutable
- это private final boolean
из AbstractUnsigned
. number
- это один из внутренних частных классов, который занимается преобразованием Base
в Shifted
и наоборот.
Что имеет значение в соответствии с предыдущим «, что я делал прошлой летней частью »
это два внутренних объекта: caster
и shiftedConstructor
:
final private Function<UnsignedNumber<N, Shifted>, Impl> caster;
final private Function<Shifted, Impl> shiftedConstructor;
Это параметризованные функции для приведения N
(или Base
) к Shifted
или для создания нового экземпляра Impl
, если текущий экземпляр реализации AbstractUnsigned<>
является неизменным.
Shifted plus(Shifted value){
return spawnBelowZero.apply(summing.apply(shifted, value));
}
В этом фрагменте показан метод добавления объекта number
. Идея состояла в том, чтобы всегда использовать Shifted
для внутреннего использования, потому что неясно, когда будут появляться положительные пределы «оригинального» типа. shifted
является внутренним параметризованным полем, которое содержит значение целого AbstractUnsigned<>
. Другие два Function<>
производных объекта приведены ниже:
final private BinaryOperator<Shifted> summing;
final private UnaryOperator<Shifted> spawnBelowZero;
Первый выполняет сложение двух Shifted
значений. И последний выполняет порождение ниже нуля транспонирования.
А теперь пример с одного из заводских шаблонов 'ад' для AbstractUnsigned<Byte, Short>
специально для упомянутых ранее spawnBelowZero
UnaryOperator<Shifted>
:
...,
v-> v >= 0
? v
: (short) (Math.abs(Byte.MIN_VALUE) + Byte.MAX_VALUE + 2 + v),
...
, если Shifted v
положительно, в действительности ничего не происходит, и возвращается исходное значение. В противном случае: необходимо вычислить верхний предел типа Base
, равный Byte
, и добавить к этому значению отрицательное значение v
. Если, скажем, v == -8
, то Math.abs(Byte.MIN_VALUE)
будет выдавать 128
, а Byte.MAX_VALUE
будет выдавать 127
, что дает 255
+ 1, чтобы получить исходный верхний предел, который был вырезан битом знака, как получил, и так желательно 256
на месте. Но самое первое отрицательное значение - это , на самом деле это 256
, поэтому снова +1
или +2
в целом. Наконец, 255 + 2 + v
, что составляет -8
, дает 255 + 2 + (-8)
и 249
Или более наглядно:
0 1 2 3 ... 245 246 247 248 249 250 251 252 253 254 255 256
-8 -7 -6 -5 -4 -3 -2 -1
И чтобы завершить все это: это определенно не облегчит вашу работу или сэкономит байты памяти, но у вас будет довольно желательное поведение, когда это необходимо. И вы можете использовать это поведение в значительной степени с любыми другими Number.class
подклассами. AbstractUnsigned
будучи подклассом Number.class
, сам предоставляет все удобные методы и константыаналогично другим «родным» подклассам Number.class
, включая MIN_VALUE
и MAX_VALUE
и многим другим, например, я кодировал удобный метод для изменяемых подклассов, называемый makeDivisibileBy(Number n)
, который выполняет простейшую операцию value - (value % n)
.
Мое первоначальное стремление было показать, что даже нуб, такой как я, может его кодировать. Моим первоначальным стремлением, когда я программировал этот класс, было получить удобный универсальный инструмент для постоянного использования.