Java Full Course: Mastering the Language Java Variables & Data Types

Java Variables & Data Types

Introduction to Java Variables and Data Types

Every Java program works with data—whether it’s a simple number, a piece of text, or a complex object. To store and manipulate this data, Java uses variables. A variable is a named memory location that holds a value of a specific data type. The data type determines what kind of data the variable can hold (e.g., integer, decimal, character) and what operations can be performed on it.

Java is a statically typed language: every variable must be declared with a type before it is used, and that type cannot change at runtime. This ensures type safety and helps catch errors early.

Data types in Java fall into two main categories:

  • Primitive types – the simplest building blocks: byte, short, int, long, float, double, char, boolean. They store raw values directly.
  • Reference types – any class, interface, array, or enumeration. They store references (memory addresses) to objects.

In the following sections, we will explore:

  • Declaring and initializing variables
  • The eight primitive types in detail (size, range, usage)
  • Type conversion (implicit and explicit)
  • Reference types and their relation to objects
  • Best practices for variable naming and scope

Java Data Types

1. Primitive Types in Java

1.1 What Are Primitive Types?

Primitive types are the most basic data types in Java. They are not objects; they hold raw values directly in memory (on the stack, not the heap). Java defines eight primitive types, which are the building blocks for data manipulation.

Characteristics:

  • Statically typed – the type of a variable must be declared and cannot change.
  • Fixed size – each primitive has a defined size (platform‑independent).
  • Default values – when used as fields, they are initialized to a default (0, 0.0, false, or '\u0000' for char).
  • Efficiency – they are stored in stack memory, making access faster than objects.

Primitive Characteristics

1.2 The Eight Primitive Types

TypeSizeDefaultRange / ValuesWrapper Class
byte1 byte0-128 to 127Byte
short2 bytes0-32,768 to 32,767Short
int4 bytes0-2³¹ to 2³¹-1 (approx -2.1e9 to 2.1e9)Integer
long8 bytes0L-2⁶³ to 2⁶³-1 (approx -9.2e18 to 9.2e18)Long
float4 bytes0.0fapprox ±3.4e-38 to ±3.4e+38 (6‑7 significant digits)Float
double8 bytes0.0dapprox ±1.7e-308 to ±1.7e+308 (15 significant digits)Double
char2 bytes'\u0000'0 to 65,535 (Unicode characters)Character
booleannot precisely definedfalsetrue or falseBoolean

1.3 Detailed Explanation of Each Primitive

a) byte

  • Size: 1 byte (8 bits)
  • Range: -128 to 127
  • Use: Memory‑sensitive applications (e.g., large arrays), file I/O, network protocols.

Example:

byte b = 100; byte maxByte = Byte.MAX_VALUE; // 127 byte minByte = Byte.MIN_VALUE; // -128

b) short

  • Size: 2 bytes (16 bits)
  • Range: -32,768 to 32,767
  • Use: Occasionally used for space‑saving in large arrays; rarely used in modern Java.

Example:

short s = 3000; short maxShort = Short.MAX_VALUE; // 32767

c) int

  • Size: 4 bytes (32 bits)
  • Range: -2,147,483,648 to 2,147,483,647
  • Use: Default choice for integer values unless memory or range constraints require another type.

Example:

int i = 100000; int maxInt = Integer.MAX_VALUE; // 2147483647 int minInt = Integer.MIN_VALUE; // -2147483648

d) long

  • Size: 8 bytes (64 bits)
  • Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
  • Use: When int range is insufficient (e.g., timestamps, large counters).
  • Literal suffix: L or l (prefer uppercase L for readability).

Example:

long l = 10000000000L; // must have L long maxLong = Long.MAX_VALUE;

e) float

  • Size: 4 bytes (32 bits)
  • Precision: Approximately 6‑7 significant decimal digits.
  • Use: When memory is critical and high precision is not required (e.g., graphics, simple physics).
  • Literal suffix: f or F.

Example:

float f = 3.14f; // must have f float pi = (float) Math.PI; // explicit cast needed

f) double

  • Size: 8 bytes (64 bits)
  • Precision: Approximately 15 significant decimal digits.
  • Use: Default choice for floating‑point numbers (e.g., scientific calculations, most real‑world values).
  • Literal suffix: Optional d or D (default).

Example:

double d = 3.141592653589793; double sqrt2 = Math.sqrt(2);

Special floating‑point values:

  • Double.POSITIVE_INFINITY
  • Double.NEGATIVE_INFINITY
  • Double.NaN (Not a Number)

g) char

  • Size: 2 bytes (16 bits)
  • Range: 0 to 65,535 (Unicode character set)
  • Use: Store a single character (letter, digit, symbol, or Unicode escape).
  • Literals: single quotes: 'A', '\u0041' (Unicode), escape sequences: '\n', '\t', '\\'.

Example:

char letter = 'A'; char unicode = '\u03A9'; // Ω char digit = '7'; char newline = '\n';

char can be treated as an integer in arithmetic (its Unicode code point):

char c = 'A'; c++; // c becomes 'B' int code = c; // 66

h) boolean

  • Size: Not precisely defined (depends on JVM), but typically 1 byte or 1 bit.
  • Values: true or false.
  • Use: Flags, conditions, logical operations.

Example:

boolean isJavaFun = true; boolean isFishTasty = false;

Note: Booleans cannot be converted to/from integers (unlike C/C++). Use if (flag) directly.

1.4 Literals and Default Values

Integer literals: can be written in decimal (default), hexadecimal (0x prefix), octal (0 prefix, discouraged), binary (0b prefix, Java 7+).

int decimal = 100; int hex = 0xFF; // 255 int binary = 0b1010; // 10

Floating‑point literals: default to double; use f suffix for float.

double d = 3.14; // double float f = 3.14f; // float

Underscores in numeric literals (Java 7+): improve readability.

int million = 1_000_000; long creditCard = 1234_5678_9012_3456L; double pi = 3.141_592_653_589_793;

Default values: When a primitive is a field (instance or static variable), it gets a default:

  • byte, short, int, long0
  • float, double0.0
  • char'\u0000'
  • booleanfalse

Local variables do not get default values; they must be initialized before use.

Variable Lifecycle Flow

1.5 Wrapper Classes

Each primitive has a corresponding wrapper class (in java.lang) that allows primitives to be used as objects (e.g., in collections).

PrimitiveWrapper
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Autoboxing and unboxing (Java 5+) automatically convert between primitives and their wrappers.

Integer i = 42; // autoboxing int j = i; // unboxing

1.6 Type Conversion and Casting

a) Implicit (Widening) Conversion

Automatically performed when converting a smaller type to a larger type (no data loss). Order: byteshortintlongfloatdouble (and char to int).

int i = 100; long l = i; // implicit double d = i; // 100.0

Automatic Widening Casting

b) Explicit (Narrowing) Casting

Required when converting a larger type to a smaller type; may lose data. Use cast operator (type).

double d = 3.14; int i = (int) d; // i = 3 (truncation) long l = 100L; byte b = (byte) l; // may overflow

c) Type Promotion in Expressions

In arithmetic expressions, smaller types (byte, short, char) are promoted to int before operation. The result is at least int; assign back with cast if needed.

byte a = 10, b = 20; // byte c = a + b; // error: a+b is int byte c = (byte)(a + b); // OK

1.7 Common Pitfalls

PitfallExplanation / Solution
Overflow/underflowArithmetic with int or long wraps around silently; use Math.addExact() etc. for detection.
Comparing floating‑point values with ==Use a tolerance: Math.abs(a - b) < 1e-9.
Using float without f suffixfloat f = 3.14; is a double → compilation error. Use 3.14f.
Assigning int to char out of rangechar can only hold values 0–65535; larger int will be truncated.
Uninitialized local variableLocal variables must be initialized before use; fields get defaults.
Using == with Boolean wrappersBoolean b1 = true; Boolean b2 = true; works due to caching, but prefer equals() for safety.

1.8 Best Practices

  • Use int for integers unless range requires long or memory constraints require short/byte.
  • Use double for floating‑point by default; use float only for large arrays or when memory matters.
  • Use boolean for flags, not integers.
  • Use final for constants: static final double PI = 3.14159;.
  • Use underscores in long numeric literals for readability.
  • Be cautious with floating‑point comparisons; use BigDecimal for precise financial calculations.
  • Prefer wrapper types in collections; prefer primitives for local variables and fields when performance matters.

1.9 Key Points to Remember

  • Java has eight primitive types: byte, short, int, long, float, double, char, boolean.
  • They are stored on the stack, not the heap.
  • Default values are given only for class fields, not local variables.
  • Widening conversions are automatic; narrowing requires an explicit cast.
  • Use wrapper classes when an object is needed (e.g., in collections).
  • Autoboxing/unboxing simplifies conversion between primitives and wrappers.

2. Reference Types in Java

2.1 What Are Reference Types?

Reference types are data types that store references (memory addresses) to objects, not the objects themselves. Unlike primitive types, which hold raw values directly in stack memory, reference types point to objects stored in the heap. When you declare a variable of a reference type, you are creating a reference that can later be assigned to an object.

Categories of reference types:

  • Classes (including abstract classes)
  • Interfaces
  • Arrays
  • Enumerations (enum)
  • Annotations (@interface)

All reference types ultimately inherit from java.lang.Object. Their default value is null.

2.2 Memory Model: Stack vs. Heap

  • Stack memory – Stores local variables and method call frames. For reference variables, the stack holds the reference (address) pointing to the object.
  • Heap memory – Stores all objects (including arrays and class instances). The object’s fields and other data reside here.

When you write:

String str = new String("Hello");
  • str is a reference stored on the stack.
  • The String object "Hello" is created on the heap.
  • str contains the memory address of that object.

If you later assign str = null;, the reference no longer points to any object; the object becomes eligible for garbage collection.

2.3 Categories of Reference Types

a) Classes

A class defines a blueprint for objects. A variable of a class type can refer to an instance of that class (or any subclass).

class Person { String name; int age; } Person p = new Person(); // p is a reference to a Person object p.name = "Alice";

Abstract classes cannot be instantiated but can be used as reference types to hold objects of concrete subclasses.

abstract class Shape { } class Circle extends Shape { } Shape s = new Circle(); // reference of abstract type

b) Interfaces

An interface reference can refer to any object of a class that implements the interface.

interface Drawable { void draw(); } class Circle implements Drawable { public void draw() { System.out.println("Circle"); } } Drawable d = new Circle(); // interface reference d.draw();

c) Arrays

Arrays are objects in Java. An array reference can point to an array of primitives or objects.

int[] numbers = new int[10]; // array of primitives String[] names = new String[5]; // array of references int[][] matrix = new int[3][4]; // 2D array (array of arrays)

d) Enumerations (enum)

Enum constants are objects; an enum variable is a reference to one of those constants.

enum Day { MON, TUE, WED } Day today = Day.MON; // reference to the MON constant

e) Annotations (@interface)

Annotations are also reference types, but they are rarely used as variable types.

2.4 The null Reference

Any reference type variable can be assigned null, meaning it does not refer to any object. Attempting to access a member (field or method) through a null reference throws a NullPointerException.

String s = null; int len = s.length(); // NullPointerException

Default value: For class fields, reference types default to null. Local variables must be initialized explicitly.

2.5 Passing by Value (Reference Semantics)

Java is strictly pass‑by‑value. For reference types, the value passed is the reference (the memory address), not the object itself.

This means you can modify the object’s state inside a method, but you cannot change the original reference to point to a different object.

public static void modify(StringBuilder sb) { sb.append(" World"); // modifies the object sb = new StringBuilder("New"); // reassigns local reference; does not affect caller } public static void main(String[] args) { StringBuilder s = new StringBuilder("Hello"); modify(s); System.out.println(s); // Hello World (object modified, but reference unchanged) }

2.6 Comparing Reference Types

  • == compares references (whether they point to the same object).
  • equals() compares the logical content (should be overridden in custom classes).
String a = new String("Hello"); String b = new String("Hello"); System.out.println(a == b); // false (different objects) System.out.println(a.equals(b)); // true (same content) String c = "Hello"; String d = "Hello"; System.out.println(c == d); // true (same string literal from pool)

2.7 Wrapper Classes (A Bridge)

Each primitive has a corresponding wrapper class that allows primitives to be used where objects are required (e.g., collections).

Integer i = 42; // autoboxing: int → Integer List<Integer> list = new ArrayList<>(); list.add(i); // works because Integer is a reference type

2.8 Strong, Soft, Weak, and Phantom References

Java also provides java.lang.ref package with special reference types for advanced memory management:

  • Strong references – normal references. Prevents garbage collection.
  • Soft references – cleared only when memory is low (useful for caches).
  • Weak references – cleared at next garbage collection cycle.
  • Phantom references – used for pre‑mortem cleanup.

2.9 Common Pitfalls

PitfallExplanation / Solution
Using == for content comparisonUse equals() for objects; == checks reference identity.
Assuming a method can swap referencesJava passes references by value; swapping local references does not affect caller.
NullPointerExceptionAlways check for null before invoking methods on objects that may be null.
Modifying objects via final referencefinal only prevents reassignment; the object’s state can still be changed.
Comparing arrays with equals()array1.equals(array2) uses Object.equals(), not content. Use Arrays.equals() for arrays.

2.10 Best Practices

  • Use equals() for content comparison.
  • Return defensive copies when exposing mutable objects.
  • Initialize reference variables.
  • Use Objects.equals() for null‑safe comparisons.
  • Prefer interface types for variables and parameters when possible (e.g., List instead of ArrayList).

2.11 Key Points to Remember

  • Reference types store addresses of objects on the heap.
  • The default value for any reference type is null.
  • Java passes references by value.
  • == compares references; equals() compares logical content.

3. Type Conversion and Casting in Java

3.1 Introduction

In Java, every expression has a type. When different types are combined in an expression or assigned to a variable, Java may need to convert one type to another. Type conversion (also called type casting) can happen automatically (implicitly) or must be written explicitly by the programmer.

3.2 Primitive Type Conversion

a) Implicit (Widening) Conversion

When a smaller type is assigned to a larger type, Java automatically performs a widening conversion. This conversion is safe because no data loss occurs.

Widening order: byteshortintlongfloatdouble charintlongfloatdouble

int i = 100; long l = i; // int → long (implicit) double d = l; // long → double (implicit)

b) Explicit (Narrowing) Conversion

When a larger type is assigned to a smaller type, data loss may occur. The programmer must use an explicit cast using the (type) operator.

double d = 3.14; int i = (int) d; // explicit cast; i becomes 3 (truncated)

c) Type Promotion in Expressions

When evaluating expressions, Java performs binary numeric promotion:

  • If either operand is double, the other is converted to double.
  • Else if either is float, the other is converted to float.
  • Else if either is long, the other is converted to long.
  • Else both are converted to int.
byte a = 10, b = 20; // byte c = a + b; // ERROR: a + b is int byte c = (byte) (a + b); // OK

3.3 Reference Type Conversion

a) Upcasting (Implicit)

Assigning a subclass object to a superclass reference (or an interface) is an upcast. It is always safe and happens automatically.

class Animal {} class Dog extends Animal {} Animal a = new Dog(); // upcast (implicit)

b) Downcasting (Explicit)

Assigning a superclass reference back to a subclass reference requires an explicit cast.

Animal a = new Dog(); Dog d = (Dog) a; // downcast (explicit)

If the object is not a Dog, a ClassCastException is thrown at runtime. Safe downcasting with instanceof:

if (a instanceof Dog) { Dog d = (Dog) a; }

c) Array Conversions

Covariant arrays: Java allows upcasting arrays, but the runtime type is preserved.

d) Interface and Class Conversions

A class reference can be cast to an interface reference if the class implements the interface.

3.4 Autoboxing and Unboxing (Java 5+)

  • Autoboxing: automatic conversion of a primitive to its corresponding wrapper class.
  • Unboxing: wrapper to primitive.
Integer i = 42; // autoboxing: int → Integer int j = i; // unboxing: Integer → int

3.5 Method Invocation Conversion

Order: Identity -> Widening primitive -> Widening reference -> Boxing -> Unboxing -> Varargs.

3.6 Common Pitfalls and Best Practices

PitfallExplanation / Solution
Loss of precision in narrowingdouble to int truncates; use Math.round() if rounding is intended.
Overflow in narrowing castlong to byte wraps around; check range before cast.
ClassCastExceptionAlways use instanceof before downcasting.

3.7 Key Points to Remember

  • Widening (implicit) – smaller type → larger type (safe).
  • Narrowing (explicit) – larger type → smaller type (may lose data).
  • Upcasting – subclass reference to superclass (implicit, safe).
  • Downcasting – superclass reference to subclass (explicit, may fail).

4. Variable Scope and Lifetime in Java

4.1 Introduction

In Java, every variable has a scope – the region of the program where it can be accessed – and a lifetime – the period during which it exists in memory.

4.2 Types of Scope

a) Class (Static) Scope

Variables declared with the static keyword.

  • Scope: Entire class and globally if public.
  • Lifetime: From class loading to class unloading.
class Counter { static int count = 0; // class variable }

b) Instance Scope

Non-static fields declared inside a class but outside any method.

  • Scope: Accessible from all instance methods.
  • Lifetime: Created when object is created, destroyed when object is GC'd.

c) Method (Local) Scope

Variables declared inside a method.

  • Scope: From declaration to the end of the method.
  • Lifetime: Executing the method (on the stack).

d) Block Scope

Variables declared inside a block ({}).

  • Scope: Within the block only.
  • Lifetime: Created when block is entered, destroyed when block exits.
if (condition) { int x = 10; // x exists only in this if block }

e) Lambda Scope (Java 8+)

Lambdas capture variables from the enclosing scope. Variables must be effectively final.

4.3 Lifetime of Variables

  • Stack variables – local variables, method parameters. Short lifetime.
  • Heap variables – instance variables and objects. Long non-deterministic lifetime.
  • Static variables – stored in method area/metaspace. Longest lifetime.

4.4 Variable Shadowing

An inner scope variable with the same name shadows the outer scope variable.

class Shadow { int x = 10; // instance variable void method() { int x = 20; // shadows instance variable System.out.println(this.x); // prints 10 (access instance via this) } }

4.5 Access Modifiers and Scope

  • private – inside the same class.
  • default – inside the same package.
  • protected – same package + subclasses.
  • public – everywhere.

4.6 Local Variable Initialization

Local variables must be initialized before use!

void method() { int x; // declared, not initialized x = 5; // now OK }

4.7 Try‑with‑Resources Scope

Resources declared in the try‑block are automatically closed and in scope only within the try block.

4.8 Common Pitfalls and Best Practices

PitfallExplanation / Solution
Using variable before initializationLocal variables must be initialized. Always assign a value before use.
Shadowing confusing readabilityAvoid reusing same name in nested scopes.

4.9 Key Points to Remember

  • Scope types: class (static), instance, method, block, lambda.
  • Local variables must be initialized before use; fields have default values.
  • Keep scope small – improves readability, reduces errors, and helps garbage collection.

\n\n# Practice Lab

Interactive Java Compiler

Loading...
Hi! Need help with studies? 👋
AI