Java Primitive Data Types
Primitive types are the most basic data types available within the Java language. The eight primitives defined in Java are : boolean, byte, short, int, long, float, double, and char. These types directly contains the value of that type, and serve as the building blocks of data manipulation in Java.
|Default||Min value||Max value||Precision||Example|
|boolean||boolean||true or false||1||false||-||-||false, true||boolean b = true;|
|byte||twos complement integer||8||0||-128||127||From -128 to +127||byte b = 65;|
|short||twos complement integer||16||0||-215||215-1||From -32,768 to +32,767||short s = 128;|
|int||twos complement integer||32||0||-231||231-1||From -2,147,483,648 to +2,147,483,647||int i = 32768;|
|long||twos complement integer||64||0L||-263||-263-1||From -9,223,372,036,854,775,808|
|long l = 255L;|
|float||IEEE 754 floating point||32||0.0f||2-149||(2-2-23)·2127||From 1.4 E-45 to 3.402,823,5 E+38||float f = 65.56f;|
|double||IEEE 754 floating point||64||0.0d||2-1074||(2-2-52)·21023||From 4.9 E-324|
to 1.797,693,134,862,315,7 E+308
|double d = 65.56d;|
|char||char||Unicode character||16||'\u0000'||0||216-1||All Unicode characters||char c = 'A';|
char c = 65;
Primitive data types are part of the core of Java, and they come with a number of operations predefined. We can't define a new operation for such primitive types.
Notice that the new keyword isn't used when initializing a variable of a primitive type. Primitive types are special data types built into the language; they are not objects created from a class. A literal is the source code representation of a fixed value; literals are represented directly in your code without requiring computation. Most of the literals are primitive type values, except String Literals, which are instance of the String class.
As shown below, it's possible to assign a literal to a variable of a primitive type:
boolean result = true; char c = 65; char capitalC = 'C'; int i = 100000; float f = 65.56f;
Overflow and Underflow
All the primitive types have a fixed size. Thus, the primitive types are limited to a range of values. A smaller primitive type (byte) can contain less values than a bigger one (long). What happens if we try to store a value that’s larger than the maximum value? We will run into a situation called overflow.
int i = Integer.MAX_VALUE; System.out.println(i); i = i + 1; System.out.println(i); System.out.println(Integer.MIN_VALUE); System.out.println(i == Integer.MIN_VALUE);
2147483647 -2147483648 -2147483648 true
When an integer overflows, it rolls over to the minimum value and begins counting up from there.
Underflow is the opposite, if we store a value smaller than the minimum value.
int i = Integer.MIN_VALUE; System.out.println(i); i = i - 1; System.out.println(i); System.out.println(Integer.MAX_VALUE); System.out.println(i == Integer.MAX_VALUE);
-2147483648 2147483647 2147483647 true
Floating point number overflow by returning Infinity. When they underflow, they return 0.0.
float f = Float.MIN_VALUE; System.out.println(f); f = f - 1f; System.out.println(f); f = Float.MAX_VALUE; System.out.println(f); f = f + 1f; System.out.println(f); System.out.println(f > Float.MAX_VALUE); System.out.println(f == Float.MAX_VALUE); f = Float.MAX_VALUE; System.out.println(f); f = f + Float.MAX_VALUE; System.out.println(f); System.out.println(f > Float.MAX_VALUE);
1.4E-45 -1.0 3.4028235E38 3.4028235E38 false true 3.4028235E38 Infinity true
Float.MIN_VALUE is the smallest positive float, so it's very close to 0. Hence Float.MIN_VALUE - 1 will be very close to -1. But since the float precision around -1 is greater than that difference, it comes out as -1. As to Float.MAX_VALUE, the float precision around this value is much greater than 1 and adding one doesn't change the result, they're so large that they get rounded back down to their original value. We'll have to add something bigger (In this case, adding Float.MAX_VALUE for Infinity).
ArithmeticException: Thrown when an exceptional arithmetic condition has occurred. For example, an integer "divide by zero" throws an instance of this class. ArithmeticException objects may be constructed by the virtual machine as if suppression were disabled and/or the stack trace was not writable.
double d1 = 100.10; double d2 = d1/0; System.out.println(d2); System.out.println(d1/d2); int i = 100; System.out.println(i/0);
Infinity 0.0 Exception in thread "main" java.lang.ArithmeticException: / by zero at com.dariawan.codes.NumberException.main(NumberException.java:12)
Floating-point math never throws exceptions. Dividing a non-zero value by 0 equals infinity. Dividing a non-infinite value by infinity equals 0.
Data Conversion (Casting)
Data conversion (casting) can happen between two primitive types. There are two type of casting:
- Implicit: casting operation is not required; the magnitude of the numeric value is always preserved. However, precision may be lost when converting from integer to floating point types
// int is converted to long, casting is not needed int i = 255; long l = i; System.out.println(l); // 255
- Explicit: casting operation required; the magnitude of the numeric value may not be preserved
// long is converted to int, casting is needed long l = 656666L; int i = (int) l; System.out.println(i); // 656666
Without casting, we'll encounter following error:
incompatible types: possible lossy conversion from long to int
The following table shows shows the casting operation for explicit conversions between primitive types, with green columns indicated implicit conversion is available:
Autoboxing and Unboxing
Each primitive data type also has a full Java class implementation that can wrap it. For instance, the Integer class can wrap an int. There is sometimes a need to convert from the primitive type to its object wrapper (e.g., using them with generics).
Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes. For example, converting an int to an Integer, a double to a Double, and so on. If the conversion goes the other way, this is called unboxing.
Here is the simplest example of autoboxing:
Character c = 'C';
Here the table of Java primitive types and their corresponding wrapper class.
|Primitive Type||Wrapper class|
Autoboxing and unboxing lets developers write cleaner code, making it easier to read. The following table lists the primitive types and their corresponding wrapper classes, which are used by the Java compiler for autoboxing and unboxing:
Why Using Primitive Data Types?
Small amount of memory
A primitive data type uses a small amount of memory to represent a single item of data. All data of the same primitive type are the same size. For example, primitive type int represents integers using 32 bits. All variables of type int use 32 bits.
Primitive Data Types == Value Types == Faster
A variable of a primitive type directly contains the value of that type (in other words, they are value types). Variables of these types live in the stack and hence are accessed fast.
On another hand, a variable of a non-primitive type doesn't contain the value directly; instead, it is a reference (similar to a pointer) to an object. (It is not possible in Java to create user-defined value types). Variables of these types live on the heap and are relatively slow to access. They have a certain overhead concerning their primitive counterparts.
So in short, primitive data types are faster. As we need to design our application well, and avoid creating unnecessary objects, the rule of of thumb is to use primitive data types as possible, except there is a reason to use their wrapper class or another type of objects.