The correct way is using ints, e.g. "int x = b[i] & 0xff;". Pretty much you should do math with int and long only. java.util.Integer/Long nowadays have static methods to work with unsigned types if you need them.
Once you remember that you need (the annoying) "0xff", you'll be free from errors. It has been there for 24 or so years. Other than that wrap your arrays into ByteBuffer and use provided utility.
The memory layout of the classes is quite immaterial here as the operation do happen within CPU registers.
[0]: https://docs.oracle.com/javase/specs/jvms/se12/html/index.ht... [1]: https://en.wikipedia.org/wiki/List_of_Java_bytecode_instruct...
static int square(int);
0: iload_0
1: iload_0
2: imul
3: ireturn
is java; compare to the c++ equivalent (used unsigned to avoid any possibility of undefined behavior): unsigned char square(unsigned char num) {
return num * num;
}
Which compiles to: mov eax, edi
imul eax, edi
ret
on x86 mul w0, w0, w0
ret
on arm.By this definition, c and c++ don't have "real" byte types either, because they undergo integer promotion for operations, and the generated code is operating on register sized values, not bytes. Java bytes use 8 bits as part of an object (with a fixed-cost padding); 8 bits as part of an array, etc. The fact that the bytecode has intermediate casts is not relevant to the actual code executed on CPU.
They're not actually operating on register-sized values: the code labelled x86 is also the x64 code, meaning GCC operates on 32b when the native registers are 64b. Likewise ARM.
Your assertion is even more debatable when Clang actually operates on 8-bit registers on x64:
mov eax, edi
mul al
ret
(it does not on ARM64 as `mul` is only defined for 32 and 64b)