Navigation
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
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'forchar). - Efficiency – they are stored in stack memory, making access faster than objects.
1.2 The Eight Primitive Types
| Type | Size | Default | Range / Values | Wrapper Class |
|---|---|---|---|---|
byte | 1 byte | 0 | -128 to 127 | Byte |
short | 2 bytes | 0 | -32,768 to 32,767 | Short |
int | 4 bytes | 0 | -2³¹ to 2³¹-1 (approx -2.1e9 to 2.1e9) | Integer |
long | 8 bytes | 0L | -2⁶³ to 2⁶³-1 (approx -9.2e18 to 9.2e18) | Long |
float | 4 bytes | 0.0f | approx ±3.4e-38 to ±3.4e+38 (6‑7 significant digits) | Float |
double | 8 bytes | 0.0d | approx ±1.7e-308 to ±1.7e+308 (15 significant digits) | Double |
char | 2 bytes | '\u0000' | 0 to 65,535 (Unicode characters) | Character |
boolean | not precisely defined | false | true or false | Boolean |
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:
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:
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:
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
intrange is insufficient (e.g., timestamps, large counters). - Literal suffix:
Lorl(prefer uppercaseLfor readability).
Example:
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:
forF.
Example:
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
dorD(default).
Example:
Special floating‑point values:
Double.POSITIVE_INFINITYDouble.NEGATIVE_INFINITYDouble.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 can be treated as an integer in arithmetic (its Unicode code point):
h) boolean
- Size: Not precisely defined (depends on JVM), but typically 1 byte or 1 bit.
- Values:
trueorfalse. - Use: Flags, conditions, logical operations.
Example:
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+).
Floating‑point literals: default to double; use f suffix for float.
Underscores in numeric literals (Java 7+): improve readability.
Default values: When a primitive is a field (instance or static variable), it gets a default:
byte,short,int,long→0float,double→0.0char→'\u0000'boolean→false
Local variables do not get default values; they must be initialized before use.
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).
| Primitive | Wrapper |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Autoboxing and unboxing (Java 5+) automatically convert between primitives and their wrappers.
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: byte → short → int → long → float → double (and char to int).
b) Explicit (Narrowing) Casting
Required when converting a larger type to a smaller type; may lose data. Use cast operator (type).
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.
1.7 Common Pitfalls
| Pitfall | Explanation / Solution |
|---|---|
| Overflow/underflow | Arithmetic 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 suffix | float f = 3.14; is a double → compilation error. Use 3.14f. |
Assigning int to char out of range | char can only hold values 0–65535; larger int will be truncated. |
| Uninitialized local variable | Local variables must be initialized before use; fields get defaults. |
Using == with Boolean wrappers | Boolean b1 = true; Boolean b2 = true; works due to caching, but prefer equals() for safety. |
1.8 Best Practices
- Use
intfor integers unless range requireslongor memory constraints requireshort/byte. - Use
doublefor floating‑point by default; usefloatonly for large arrays or when memory matters. - Use
booleanfor flags, not integers. - Use
finalfor constants:static final double PI = 3.14159;. - Use underscores in long numeric literals for readability.
- Be cautious with floating‑point comparisons; use
BigDecimalfor 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:
stris a reference stored on the stack.- The
Stringobject"Hello"is created on the heap. strcontains 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).
Abstract classes cannot be instantiated but can be used as reference types to hold objects of concrete subclasses.
b) Interfaces
An interface reference can refer to any object of a class that implements the interface.
c) Arrays
Arrays are objects in Java. An array reference can point to an array of primitives or objects.
d) Enumerations (enum)
Enum constants are objects; an enum variable is a reference to one of those constants.
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.
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.
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).
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).
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
| Pitfall | Explanation / Solution |
|---|---|
Using == for content comparison | Use equals() for objects; == checks reference identity. |
| Assuming a method can swap references | Java passes references by value; swapping local references does not affect caller. |
NullPointerException | Always check for null before invoking methods on objects that may be null. |
Modifying objects via final reference | final 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.,
Listinstead ofArrayList).
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:
byte → short → int → long → float → double
char → int → long → float → double
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.
c) Type Promotion in Expressions
When evaluating expressions, Java performs binary numeric promotion:
- If either operand is
double, the other is converted todouble. - Else if either is
float, the other is converted tofloat. - Else if either is
long, the other is converted tolong. - Else both are converted to
int.
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.
b) Downcasting (Explicit)
Assigning a superclass reference back to a subclass reference requires an explicit cast.
If the object is not a Dog, a ClassCastException is thrown at runtime.
Safe downcasting with instanceof:
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.
3.5 Method Invocation Conversion
Order: Identity -> Widening primitive -> Widening reference -> Boxing -> Unboxing -> Varargs.
3.6 Common Pitfalls and Best Practices
| Pitfall | Explanation / Solution |
|---|---|
| Loss of precision in narrowing | double to int truncates; use Math.round() if rounding is intended. |
| Overflow in narrowing cast | long to byte wraps around; check range before cast. |
ClassCastException | Always 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.
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.
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.
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!
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
| Pitfall | Explanation / Solution |
|---|---|
| Using variable before initialization | Local variables must be initialized. Always assign a value before use. |
| Shadowing confusing readability | Avoid 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
Write, compile, and run code in your browser
