# Java Data Types at a glance

# Java Data Types

Java requires specifying the data type when declaring a variable.  
This has two goals:

* To know how many memory locations are used.
    
* To know which operations can be performed on the data. The basic operations are addition, subtraction, multiplication, division and remainder or modulo: `+`, `-`, `*`, `/`, `%`.
    

A **primitive type** is a data type that is defined directly in the Java programming language.

## Primitive types

As in almost all programming languages, Java has the following primitive data types:

* For integer numbers: `byte`, `short`, `int`, `long`.
    
* For floating‑point numbers: `float` and `double`.
    
* For characters: `char`.
    
* For logical values (only two possible values): `boolean`.
    

In total, Java has eight primitive types: `byte`, `short`, `int`, `long`, `char`, `float`, `double` and `boolean`.

`String` is the only basic predefined data type that is widely used but **is not** a primitive type; it is a class.

## Wrapper types

A **wrapper type** is a predefined class that encapsulates a value of a primitive type:

* `Integer` wraps the primitive type `int`.
    
* `Long` wraps the primitive type `long`.
    
* `Character` wraps the primitive type `char`.
    
* `Float` wraps the primitive type `float`.
    
* `Double` wraps the primitive type `double`.
    
* `Boolean` wraps the primitive type `boolean`.
    
* `Byte` wraps the primitive type `byte`.
    
* `Short` wraps the primitive type `short`.
    

## Difference between primitive types and other data types

Primitive types are not objects and therefore do not have an associated class directly.

```java
int monthNumber = 5;
System.out.println(monthNumber);
```

In data structures that require objects, such as map keys, you must use wrapper types. For example, the key can be of type `Integer`, but not of type `int`.

The following is incorrect:

```java
Map<int, String> months = new HashMap<>();   // Incorrrect, because a map's key must be an object
```

This, however, is correct:

```java
int monthNumber = 5;
Map<Integer, String> months = new HashMap<>();
months.put(monthNumber, "May");
```

In this and similar cases, the compiler automatically converts the primitive type `int` into the wrapper type `Integer`. This technique is called *autoboxing* (and the reverse conversion is called *unboxing*).

All Java objects inherit from the `Object` class. Since primitive types are not objects, they cannot use methods from `Object`, such as `equals()`, `wait()` or `notify()`.

## Objects and references

Objects are normally created with the `new` keyword (or via constructors and factories). Primitive types are declared simply using their type and the variable name.

```java
int monthNumber = 5;
Integer otherMonthNumber = new Integer(5); // nowadays Integer.valueOf(5) is preferred
```

In other words, a reference‑type variable stores a **reference** to a memory area where the object is located. A primitive‑type variable stores the value directly in the memory associated with that variable.  
A reference is conceptually similar to a pointer in the C language, although in Java it cannot be manipulated explicitly.

## Data types and memory

* The data type that uses the least memory is `byte`, which occupies only one byte. Its value range goes from -128 to 127.
    
* `short` occupies two bytes and its range goes from -32,768 to 32,767 (`Short.MIN_VALUE` to `Short.MAX_VALUE`).
    
* `int` occupies four bytes and its range goes from -2,147,483,648 to 2,147,483,647 (`Int.MIN_VALUE` to `Int.MAX_VALUE`).
    
* `long` occupies eight bytes and its range goes from `Long.MIN_VALUE` to `Long.MAX_VALUE`.
    

Notice the symmetry in signed integer types: the total number of negative values is equal to the total number of positive values, considering that zero is counted on the positive side.  
Unlike languages such as C or C++, Java does not have `unsigned` integer types.

* `char` occupies two bytes and represents values from 0 to 65,535 (Unicode characters).
    
* `float` occupies four bytes.
    
* `double` occupies eight bytes.
    
* The specification does not fix the physical size of `boolean`; logically it represents only two possible values (`true` and `false`).
    

## Syntax for `long`, `float` and `double` literals

* A `long` literal may have the suffix `L` or `l` (it is recommended to use `L` to avoid confusion with the digit 1).
    
* A `float` literal must have the suffix `F` or `f`.
    
* A `double` literal may have the suffix `D` or `d`, although it is usually omitted because `double` is the default type for floating‑point literals.
    

```java
long   x = 1L;
float  f = 1.0f;
double y = 1.0;
```

## Digit separator

* The separator between integer and fractional part is the dot, and it is mandatory in decimal numbers.
    
* The thousands separator may be an underscore `_`. It is optional, but improves readability of large numbers. It was introduced in Java 7.
    

```java
long   x = 1_000_000L;
double y = 1_000_000.0;
```

## Scientific notation

* The exponent is written using the letter `e` or `E`.
    
* In scientific notation, the literal becomes a `double` by default, unless it is explicitly marked as `float`.
    

```java
double y = 1e6;   // 1.0 × 10^6
double z = 1e6D;  // equivalent to the line above
float  f = 1e6f;  // float
```

(Using `e` with a `long` literal is not correct; `1e6` is a floating‑point literal, not a `long`.)

## Precision of decimal numbers

* To use `float`, the literal must end with `f` or `F`.
    
* To use `double`, you may use `d` or `D`, but in practice it is usually omitted, since `double` is the default type.
    

```java
float  f = 3.1415927f;
double d = 3.141592653589793;
```

The `float` and `double` types represent floating‑point numbers in binary, and therefore they cannot represent exactly most decimal fractions that are simple in base 10.

## Type promotion

Java performs numeric promotion automatically when primitive numeric types are used in expressions with operators like +, -, \*, /, %, or in comparisons.

Core Rules

* byte, short, char → always promoted to int first
    
* If any operand is long → both promoted to long
    
* If any operand is float → both promoted to float
    
* If any operand is double → both promoted to double
    

```java
byte b = 100;
short s = 200;
// b + s promotes to int (compile error if assigned to byte/short)
int result = b + s;  

long big = 1_000_000L;
int small = 42;
long total = big + small;  // int -> long
```

## `BigInteger` and `BigDecimal` classes

The `BigInteger` and `BigDecimal` classes are used to represent very large numbers or to perform calculations with high numeric precision.

In the case of `BigDecimal`, numbers are represented in decimal form (not as binary floating point), which allows arithmetic operations without the typical rounding errors of `float` and `double` when working, for example, with money.

```java
// float/double PROBLEM (binary floating point)
double money = 0.1 + 0.2;  // 0.30000000000000004 ❌

// BigDecimal SOLUTION (decimal arithmetic)  
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal money = bd1.add(bd2);  // 0.3 exactly ✅
```

BigDecimal stores decimal digits + scale, not fractions p/q or rational numbers: 1/3 becomes "0.333333" (truncated/rounded), not the exact rational 1/3.

* Therefore, division operations always require **scale** and **rounding** modes.
    

## Identity and equality

For primitive types, the value is the data itself; two primitive variables are “equal” if they contain the same value.  
For reference types (objects), **identity** is determined by the reference (the logical address of the object), and **logical equality** is usually defined by the `equals()` method.

## Autoboxing

As the official documentation says:

> The result of all this magic is that you can largely ignore the distinction between int and Integer, with a few caveats. An Integer expression can have a null value. If your program tries to autounbox null, it will throw a NullPointerException. The == operator performs reference identity comparisons on Integer expressions and value equality comparisons on int expressions. Finally, there are performance costs associated with boxing and unboxing, even if it is done automatically.

## When to use primitive types

* The use of primitive types in modern Java is not just about maintaining backward compatibility (autoboxing and unboxing techqniques were introduced in Java 5).
    
* In some cases, it's preferable to use primitive types for performance reasons, for example in big loops or in web applications with a very large volume of users.
    

# Kotlin Data Types

Kotlin only works with **objects**, not with primitives in the Java sense.

* Types like `Int`, `Long`, `Double`, `Boolean`, `Char`, etc. are regular classes from the Kotlin point of view, and you can call methods and properties on them (`10.toString()`).
    
* There is no separate `int`/`Integer` distinction in the syntax like in Java; you always write `Int`, `Long`, `Double`, and so on.
    

## What happens at runtime

* On the JVM, the compiler **optimizes** these types to real JVM primitives (`int`, `long`, `double`, etc.) whenever possible, so performance is equivalent to using primitives in Java.
    
* When a primitive‑like value must behave as an object (for example, when used in a generic type or stored in a `List<Int>`), the compiler uses the corresponding wrapper (`java.lang.Integer`, `java.lang.Long`, etc.), boxing and unboxing automatically. Primitive representations exist only as an implementation detail for efficiency on the JVM platform.
    

## Unsigned types

Kotlin supports unsigned integer types (UInt, ULong, etc.) for positive-only values without sign bit. Java lacks these, as we have explained above.
