Java Data Types

In Java, all data types are separated into two groups: primitive data types and reference types.

Java provides only eight primitive data types. They are built-in in the language syntax as keywords. The names of all primitive types are lowercase. The most commonly used type is int which represents an integer number.

int num = 100;

The number of reference types is huge and constantly growing. A programmer can even create their own type and use it like any other standard type. The most frequently used reference types are StringScanner and arrays. Remember that Java, like most programming languages, is case sensitive.

In this topic, we will focus on String, which is a common example of the reference type.

The new keyword

In most cases, an object of a reference type can be created using the new keyword. When we use the new keyword, the memory is allocated for the object we create. This is called instantiation of the object because we create an instance of it. Then we initialize the variable by assigning some value to it. Often, as in our example, it is done with one line.

String language = new String("java"); 
// instantiation of String and initialization with "java"

You can also use a literal for strings:

String language = "java";

The first approach with the keyword new is common for reference data types, while the second is only string-specific. Both approaches give us the same result for strings but they have some technical differences which we will not consider here.

The main difference

The basic difference between primitive and reference types is that a variable of a primitive type stores the actual values, whereas a variable of a reference type stores an address in memory (reference) where the data is located. The data can be presented as a complex structure that includes other data types as their parts.

The following picture simply demonstrates this difference. There are two main memory spaces: stack and heap. All values of primitive types are stored in stack memory, but variables of reference types store addresses of objects located in heap memory.

stack and heap diagram

We will not consider stack and heap in detail here. Just remember this difference between primitive and reference types.

Assignment

The way to store data also affects the mechanism to assign the value of a variable to another variable:

  • primitive types: the value is just copied;
  • reference types: the address to the value is copied (the same data is shared between several variables).

Here is a snippet of code and a picture that demonstrates this:

int a = 100;
int b = a; // 100 is copied to b

String language = new String("java");
String java = language;

The variable b has a copy of the value stored in the variable a. But the variables language and java reference the same value, rather than copying it. The picture below clearly demonstrates the difference.

stack and heap

Just remember, when assigning one value of a reference variable to another, we just make a copy of a reference rather than the value itself.

Comparisons

Comparing reference types using == and != is not the same as comparing primitive types. Actually, when you are comparing two variables of the String type, it compares references (addresses) rather than actual values.

The following code showcases it clearly:

String s1 = new String("java");
String s2 = new String("java");
String s3 = s2;

System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

The picture below demonstrates how this effect occurs:

Comparing reference types

So, you should not use comparison operators when you want to compare the values of reference types. The correct way to compare strings is to invoke the equals method.

String s1 = new String("java");
String s2 = new String("java");
String s3 = s2;

System.out.println(s1.equals(s2)); // true
System.out.println(s2.equals(s3)); // true

The null type

Unlike primitive types, a variable of a reference type can refer to a special null value that represents the fact that it is not initialized yet or doesn't have a value.

String str = null;
System.out.println(str); // null
str = "hello";
System.out.println(str); // hello

The following statement with a primitive type won't compile.

int n = null; // it won't compile

Unfortunately, the frequent use of the null value can easily lead to errors in the program and complicate the code. Try to avoid null whenever it is possible and only use it if you really need it.

Floating-point types

A floating point numeral refers generally to the notation of a number which contains an integer part, a fractional part and their separator (for example 0.01 or 3.1415). Such numbers represent fractions which are often used in science, statistics, engineering, and many other fields.

Java has two basic types to represent such numbers: float and double. They are called floating-point types. In fact, these types cannot represent an arbitrary decimal number, because they support only a limited number of significant decimal digits (6-7 for float type, and 14-16 for double type). In addition, double can represent a wider range of numbers than float.

In practice, programmers mostly use the double type as it has higher precision compared to float. But all the information below is valid for the float type as well.

Declaring variables and assigning values

In a program, the double literal looks like 5.2 where the dot character separates the integer and fractional parts of a number.

Here are a few examples of several initialized double variables:

double zero = 0.0;
double one = 1.0;
double negNumber = -1.75;
double pi = 3.1415;

System.out.println(pi); // 3.1415

It is also possible to assign an integer value to a double variable:

double one = 1;
System.out.println(one); // 1.0

If you want to declare and initialize a float variable, you must mark the assigned value with the special letter f (float literal):

float pi = 3.1415f;
float negValue = -0.15f;
System.out.println(pi); // 3.1415 without f

Both types can store only a limited number of significant decimal values:

float f = 0.888888888888888888f; // a value with a lot of decimal digits
System.out.println(f);           // it only prints 0.8888889

Floating-point types have a specific way to mark values with a mantissa:

double eps = 5e-3; // means 5 * 10^(-3) = 0.005
double n = 0.01e2; // means 0.01 * 10^2 = 1.0

Arithmetic operations

You can perform all types of arithmetic operations with floating-point types.

double one = 1.0;
double number = one + 1.5; // 2.5

double a = 1.75;
double b = 5.0;
double c = b - a; // 3.25

double pi = 3.1415;
double squaredPi = pi * pi; // 9.86902225

For double and float operands, the / operator performs a floating-point division, not an integer division.

System.out.println(squaredPi / 2); // 4.934511125

Pay attention to an important phenomenon that beginners often miss:

double d1 = 5 / 4; // 1.0
double d2 = 5.0 / 4; // 1.25

In the first case, we perform integer division that produces an integer result and then assign the result to d1. Whereas in the second case, we perform a real division that produces a double value and then assign the value to d2.

Errors during computations

Be careful! Operations with floating-point numbers can produce an inaccurate result:

System.out.println(3.3 / 3); // prints 1.0999999999999999

Errors can accumulate during computation. In the following example we calculate the sum of ten decimals 0.1:

double d = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
System.out.println(d); // prints 0.9999999999999999

Such errors happen because floating point numbers are actually stored and operated in binary form and not all real numbers can be represented exactly (similarly, we cannot represent the exact value of the 1/3 fraction in decimal form). In the following topics, we will find out how to deal with this issue. For now, just consider this shortcoming.

Reading floating-point numbers

You can use a Scanner to read the values of both floating-point types from the standard input.

Scanner scanner = new Scanner(System.in);

float f = scanner.nextFloat();
double d = scanner.nextDouble();

As an example, consider a program that calculates the area of a triangle. To find it, the program reads the base and the height from the standard input, then multiplies them, and divides the result by 2. Note that the base and the height are perpendicular to each other.

import java.util.Scanner;

public class AreaOfTriangle {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        double base = scanner.nextDouble();
        double height = scanner.nextDouble();

        double area = (base * height) / 2;

        System.out.println(area);
    }
}

Let's calculate the area of a triangle with a base of 3.3 meters and a height of 4.5 meters.

Input 1:

3.3 4.5

Output 1:

7.425

As you can see, it's area is 7.425 square meters!

Keep in mind that the output of this program may have a lot of zeros like the output below because an operation with floating-point numbers can produce inaccurate results.

Input 2:

2.2 4.01

Output 2:

4.4110000000000005

It is possible to round or format a double result, but we will not do it in this lesson.

The decimal separator

If you try to execute previous snippets of code locally, you may encounter a problem with your computer having different locale settings. In this case, the Scanner cannot read floating-point numbers with the dot character (3.1415). Try to input numbers written with the comma separator (3,1415).

If you want to use the dot character without modifying your local settings, try using the following code to create a scanner:

Scanner scanner = new Scanner(System.in).useLocale(Locale.US);

Java data types and their sizes

There are several groups of basic types divided by meaning. Types from the same group operate similarly, but they have different sizes and, as a result, represent different ranges of values.

You do not need to know all this information by heart because it is easy to find it in the documentation or Google it. But a common understanding of these concepts is essential in job interviews and practice.

Numbers

Java provides several types for integers and fractional numbers. These basic data types are often used in arithmetic expressions.

Integer numbers (0, 1, 2, ...) are represented by the following four types: long, ⁣intshort, and byte(from the largest to the smallest). These types have different sizes and may represent different ranges of values. The range of an integer type is calculated as −(2^(n−1)) to (2^(n−1)−1, where n is the number of bits. The range includes 0, so we subtract 1 from the upper bound.

  • byte: size 8 bits (1 byte), ranging from −128 to 127;
  • short: size 16 bits (2 bytes), ranging from −32768 to 32767;
  • int: size 32 bits (4 bytes), ranging from −(231) to (231)−1;
  • long: size 64 bits (8 bytes), ranging from −(263) to (263)−1.

The sizes of specific types are always the same. They do not depend on the operating system or hardware and cannot be changed.

The most commonly used integer types are int and long. Try to use int if it suits your purposes. Otherwise, use long.

int one = 1;
long million = 1_000_000L;

Floating-point types represent numbers with fractional parts. Java has two such types: double (64 bits) and float (32 bits). These types can store only a limited number of significant decimal digits (~6-7 for float and ~14-16 for double). Usually, you will use the double data type in practice.

double pi = 3.1415;
float e = 2.71828f;

When you declare and initialize a float variable, mark the assigned value with the special letter f. Similarly, a long value is marked with the letter L.

Characters

Java has a type named char to represent letters (uppercase and lowercase), digits, and other symbols. Each character is just a single letter enclosed within single quotes. This type has the same size as the short type (2 bytes = 16 bits).

char lowerCaseLetter = 'a';
char upperCaseLetter = 'Q';
char dollar = '$';

Characters represent symbols from many languages, including hieroglyphs and other special symbols.

Booleans

Java provides a type called boolean, which can store only one of two values: true or false. It represents only one bit of information, but its size is not precisely defined.

boolean enabled = true;
boolean bugFound = false;

You will often use boolean type in conditionals and as a result of comparing two numbers.

Conclusion

  • There are several types of integers and fractional numbers;
  • Integer numbers are represented by long, ⁣int, ⁣short, and byte;
  • Floating-point types represent numbers with fractional parts: double (64 bits) and float (32 bits);
  • char data type represents letters (uppercase and lowercase), digits, and other symbols;
  • boolean data type stores only true and false values.

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate