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.

CategoryTypeDescriptionSize
(bits)
DefaultMin valueMax valuePrecisionExample
booleanbooleantrue or false1false--false, trueboolean b = true;
Arithmatic
(Integral)
bytetwos complement integer80-128127From -128 to +127byte b = 65;
shorttwos complement integer160-215215-1From -32,768 to +32,767short s = 128;
inttwos complement integer320-231231-1From -2,147,483,648 to +2,147,483,647int i = 32768;
longtwos complement integer640L-263-263-1From -9,223,372,036,854,775,808
to +9,223,372,036,854,775,807
long l = 255L;
Arithmatic
(Floating-point)
floatIEEE 754 floating point320.0f2-149(2-2-23)·2127From 1.4 E-45 to 3.402,823,5 E+38float f = 65.56f;
doubleIEEE 754 floating point640.0d2-1074(2-2-52)·21023From 4.9 E-324
to 1.797,693,134,862,315,7 E+308
double d = 65.56d;
charcharUnicode character16'\u0000'0216-1All Unicode characterschar 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.

Literals

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);
                    

Will output:

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);
                    

With output:

-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);
                    

With output:

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

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);
                    

With output:

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:

Frombooleanbyteshortintlongfloatdoublechar
to booleanN.AN.AN.AN.AN.AN.AN.AN.A
to byteN.A-(byte)(byte)(byte)(byte)(byte)(byte)
to shortN.A-(short)(short)(short)(short)(short)
to intN.A-(int)(int)(int)
to longN.A-(long)(long)
to floatN.A-(float)
to doubleN.A-
to charN.A(char)(char)(char)(char)(char)(char)-

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 TypeWrapper class
booleanBoolean
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter

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.

This article is part of Java Data Types Series.

Other articles in this series: