3

Background:

I'm Java newbie. I'm working on a data logger project, where I'm using many data types regarding to expected data range and precision to reduce storage, memory and CPU usage because it will run on device with limited resources.

For example - to store temperature I'm using 8 bit data type, to store some other things I'm using 16-bit short and I have double data type in few cases.

For now (for best performance) I'm using byte array and ByteBuffer and I put my measurements with diffrent types in it, byte after byte. There is another byte array where I store byte buffer indexes with information which sample is where and which data type is it. It performs very well, but it is complicated to debug, I have separate functions that do the same thing but on diffrent data types etc.

What I'm trying to do

I want to create list of all measurements with diffrent data types:

Double someDouble = 0.24;
Integer someInteger = 234;
Long someLong = 253263632L;

List<Number> measurements = new ArrayList<>();
measurements.add(someDouble);
measurements.add(someInteger);
measurements.add(someLong);

// later I have to multiply all measurements by their multipliers
// and do some other math stuff
// My goal is to do all math in one loop without care about data type
foreach(Number n: measurements) {
    doSomeMathOnNumber(n);
}

My question:

What happens inside Number class when I assign these 3 data types to it?

More precisely:

  • is there memory overhead?
  • can I put Double there and expect no precision loss when I will try to get it back as Double?
Kamil
  • 13,363
  • 24
  • 88
  • 183
  • 3
    "*For now (for best performance) I'm using byte array and ByteBuffer and I put my measurements with diffrent types in it, byte after byte.*" - Don't do that. Just use `int` for everything up to 32 bits and `long`, `float` and `double` as needed. – Turing85 Jul 04 '18 at 23:30
  • 2
    You should read up on the difference between variables and objects (and classes, maybe) and also generics. Your code keeps the Double, Integer and Long objects exactly as is (they don't get assigned to Number objects - List can store objects of those types just fine). – Bernhard Barker Jul 04 '18 at 23:36
  • 2
    Following up on @Dukeling 's comment, you may also want take a look at [Autoboxing](https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html). – Turing85 Jul 04 '18 at 23:38
  • Maybe duplicate: [When casting a derived class to a parent class do you lose data?](https://stackoverflow.com/q/18902298) [Does a child object lose its unique properties after casting back and forth between a parent class](https://stackoverflow.com/q/16224277) – Bernhard Barker Jul 04 '18 at 23:42
  • So... According to @Dukeling comment - I'm wondering if Number class creates instance of required type, holds reference to it and gives few methods to access data? – Kamil Jul 04 '18 at 23:55
  • 1
    No, it doesn't do any of those things. You aren't creating `Numbers` at all here, and they don't create themselves. You are creating `Double`, `Integer`, and `Float`, and they all have `Number` as a base class. You aren't assigning anything to `Number`, you are passing a `Double` etc. by reference: and there is no process occurring via which precision could possibily be lost. – user207421 Jul 05 '18 at 02:53

1 Answers1

2

tl;dr

is there memory overhead?

  • Yes, objects take more memory than primitives.
  • Auto-boxing has a tiny bit of cost in both memory and performance.

What happens inside Number class when I assign these 3 data types to it?

Nothing special happens when you assign a Integer, Long, or Double object to your List<Number>.

But something special is happening when you assign a primitive to an object of its wrapping class: auto-boxing of primitives into objects. This occurs in your first three lines where you populate your Double, Integer, and Long object by assigning a primitive value.

If your environment is so severely constrained (limited in memory), you should not be using any of those initial-cap class names: Byte, Short, Integer, Long, Float, Double, Number. Use primitive arrays with primitive types. But do use the types (discussed below), rather than invent your own bit-by-bit byte-by-byte management unless you have a very clear proven reason.

can I put Double there and expect no precision loss when I will try to get it back as Double?

There is no conversion when a Double object is held as a Number. A Double object is a Number already, by inheritance. Every Double object is a Number, but not every Number is a Double.

Details

Number is an abstract class (see Tutorial), meaning that it is not meant to be directly instantiated. It is designed to be subclassed, and those subclasses in turn can be instantiated.

You need to learn the difference between primitive types and object types. Java offers both kinds of type systems. Primitive values (see Tutorial) are not object-oriented. Primitives were designed into Java to facilitate (a) programmers not skilled in OOP to learn the new Java language, and (b) porting code from other languages that have a similar type system (such as C). Also, primitives have the advantages of taking little memory and being fast to work with. Objects, in contrast, take more memory and are not as fast in execution to work with, but are much more flexible and sophisticated.

Some people have argued that you can design a programming language to have the best of both, exposing only object types while backing some of them with primitive types… but that is not what Java (currently) does, so here we will set that topic aside.

Java mixes the primitive and object types within the same code. There are even equivalent object types for each of the numeric primitives. Notice the letter case conventions, where lowercase indicates primitive type while initial uppercase indicates object type. Each of these initial-cap classes listed below are subclass of Number.

  • For the primitive byte, we have the class Byte, both 8-bit (octet) signed integer number holders, with a range of -128 to 127 (inclusive).
  • For the primitive short, we have the class Short, both 16-bit signed integer number holders, with a range of -32,768 to 32,767 (inclusive).
  • For the primitive int, we have the class Integer, both 32-bit signed integer number holders, with a range of -231 to a maximum value of 231-1 (roughly ± 2 billion).
  • For the primitive long, we have the class Long, both 64-bit signed integer number holders, with a range of -263 and a maximum value of 263-1.
  • For the primitive float, we have the class Float, both 32-bit signed floating-point numbers.
  • For the primitive double, we have the class Double, both 64-bit signed floating-point numbers.

Why do we bother with the wrapper classes if we already have the primitives? For compatibility with other code that expects objects. The biggest example is the Java Collections Framework.

Recent generations of Java added auto-boxing to bridge the gap between the type systems by generating conversion code at compile time. Auto-boxing converts primitive data types to their matching wrapper class.

Auto-boxing was added to make life easier for human-programmers, but is more work for the computer at runtime. Boxing means looking up the matching class, instantiating an object of that class, and assigning the primitive’s value to that object. Unboxing means the reverse, the value must be extracted from the object and placed in memory where the primitive lives.

In many apps, this overhead added by boxing-and-unboxing is negligible to the overall performance of the app. But in more extreme cases where large counts of numbers are being processed often, a programmer may decide to avoid the boxing, avoid the objects, and use only primitives.

Double someDouble = 0.24;
Integer someInteger = 234;
Long someLong = 253263632L;

In each of the three lines above, you have a primitive value on the right being auto-boxed into an object on the left. The auto-boxing feature of Java makes this look nearly invisible, because on conventional computer environments we generally do not care about the hit on performance and memory involved with auto-boxing. But if programming for constrained environments, you may want to avoid the Number objects and the List objects. But then you give up the convenience of the polymorphism (treating Double & Integer & Long all as Number).

Likewise, I assume your doSomeMathOnNumber(n) method is doing some unboxing, going from objects back to primitives. More memory and CPU cycles used.

(for best performance) I'm using byte array and ByteBuffer and I put my measurements with diffrent types in it,

While I am not an expert in programming in constrained environments, I would guess you are working too hard. I suspect simple Java arrays holding the byte, short, etc. types would meet your needs.

For more info, search Stack Overflow for Question such as this, Why do we use autoboxing and unboxing in Java?.


By the way, the floating-point types trade away accuracy for speed of execution. They are not appropriate for matters where accuracy matters, such as tracking money. For such matters, use the BigDecimal class, slower but accurate.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154