Java Programming
Classes, Objects, Memory, Lifecycle and Wrapper Types
PGCP-BDA
class and object
A class defines state and behavior; an object is a runtime instance with identity and fields governed by that class.
object identity
is tests whether references identify the same object, while == asks objects for value equality.
stack frame and heap object
A method call owns a stack frame of local state; dynamically created objects live on the heap and are reached through references.
fields and methods
Fields hold object or class state, while methods implement operations using parameters, local variables and access to permitted fields.
static member
A field or method associated with a class rather than an individual object and accessed through the class context.
object lifecycle
An object is allocated, initialized, used while reachable and becomes eligible for garbage collection after it is unreachable.
garbage collection
Java garbage collection reclaims heap objects that are no longer reachable from live roots
wrapper object
An immutable object such as Integer or Double that represents a primitive value and provides parsing and utility operations.
autoboxing and unboxing
Autoboxing converts a primitive to its wrapper and unboxing extracts a primitive, with possible allocation, identity surprises and null failures.
Static members
A static field belongs to the class's runtime state rather than each instance. Use it for genuine class-wide information, not as a shortcut for global mutable state.
public final class Employee {
private static long nextId = 1;
private final long id;
public Employee() {
id = nextId++;
}
}
This counter demonstrates shared state but is not thread-safe. Concurrent construction can lose updates. Production identity generation may require synchronization, an atomic type, a database sequence or another authority.
Static methods can directly access static members. They need an explicit object reference to access instance members. Call static methods through the class name to make ownership clear.
Object reachability
Java's garbage collector reclaims heap memory for objects that are no longer reachable from garbage-collection roots. Roots include active thread stacks, live static fields and references maintained by the JVM or native code. Reachability follows chains of strong references.
Account first = new Account(10);
Account second = first;
first = null; // object remains reachable through second
second = null; // eligible if no other reachable reference exists
Assigning one reference to null does not destroy an object. It only removes that particular path. Eligibility also occurs through reassignment, scope exit or when the containing object itself becomes unreachable.
The commonly demonstrated eligibility patterns all follow the same reachability rule. Nulling removes one root path by assigning null. Reassignment makes a variable point to another object, possibly leaving the old object unreachable. An island of isolation is a group of objects that still reference one another but has no reference path from any live root. Mutual references inside the island do not keep it alive.
Member inner classes
A non-static member inner class is tied to an enclosing instance:
class Cart {
private int itemCount;
class Summary {
String text() {
return "Items: " + itemCount;
}
}
}
Cart cart = new Cart();
Cart.Summary summary = cart.new Summary();
Summary can directly access Cart's fields, including private fields, through its implicit outer reference. Inside the inner class, Cart.this explicitly names that enclosing object. This relationship has a memory consequence: retaining the inner object also retains its outer object. Prefer a static nested class when the outer instance is unnecessary.
Static nested classes
A static nested class is associated with the enclosing class, not with an enclosing object:
class Order {
private final int number;
Order(int number) { this.number = number; }
static class Validator {
boolean isValidNumber(int value) {
return value > 0;
}
}
}
Order.Validator validator = new Order.Validator();
It has no implicit Order.this reference and cannot directly access an individual Order's instance fields. Like other class members, it can access private members when it has an object through which to do so. Static nested classes are useful for builders, result objects, comparators and implementation helpers that conceptually belong to the outer type.
Wrapper classes
Every primitive has a wrapper class:
| Primitive | Wrapper |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Wrappers are immutable objects. They are required by generic collections, which cannot use primitive type arguments and they provide conversion methods, constants and utility operations.
Boxing converts a primitive to its wrapper; unboxing extracts the primitive:
Integer boxed = 42; // boxing
int value = boxed; // unboxing
If boxed is null, unboxing throws NullPointerException. Hidden unboxing also occurs in arithmetic, comparisons with primitives, switch expressions and conditional expressions. Validate nullable wrappers before such use.
Wrapper identity and numeric comparison
Autoboxing may reuse cached wrapper instances, making some == comparisons appear to work:
Integer x = 100;
Integer y = 100;
Integer p = 1000;
Integer q = 1000;
System.out.println(x == y); // commonly true by required Integer cache range
System.out.println(p == q); // normally false
Identity is not numeric equality. Use equals for equal wrapper type and value or unbox deliberately when primitive comparison is intended. Note that Integer.valueOf(1).equals(Long.valueOf(1)) is false because wrapper equality includes the wrapper type.
Floating-point values need special thought: NaN, infinities, signed zero and rounding make direct calculations different from exact decimal arithmetic. Financial values commonly require BigDecimal constructed from a decimal string.
Memory lifetime versus resource lifetime
Garbage collection releases managed heap memory. It does not provide timely release of operating-system resources such as files, sockets, locks or database connections. Those resources require deterministic cleanup:
try (BufferedReader reader = Files.newBufferedReader(path)) {
System.out.println(reader.readLine());
} // close() is invoked even when the body throws
Try-with-resources works with AutoCloseable resources and closes them in reverse declaration order. This is preferable to waiting for garbage collection. The historic finalization mechanism is deprecated and unreliable for resource cleanup.
Keep these similar terms separate:
finalrestricts reassignment, overriding or inheritance;finallyis a block associated with exception control flow;finalizerefers to the obsolete finalization facility.
String pool and object identity
String literals are interned. Equal literals in the same runtime commonly refer to the same pooled object:
String a = "java";
String b = "java";
String c = new String("java");
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a.equals(c)); // true
== compares reference identity: whether both variables hold the same object reference. equals compares String content. Code must not use pool behaviour as a substitute for value comparison. intern() returns a canonical pooled representation, but routine application comparison should still express its intent with equals.
For a possibly null variable, "ready".equals(status) safely returns false. Objects.equals(left, right) is a clear null-safe comparison for two variables.
References and aliasing
BankAccount first = new BankAccount("A1", 1_000);
BankAccount alias = first;
first and alias are separate variables holding references to the same object. Mutation through one reference is visible through the other. first == alias compares reference identity. Assigning alias = null changes only that variable.
An object is not automatically copied when its reference is assigned, passed, returned or stored in a field. API documentation must state whether it retains, copies or transfers mutable data.
Practical considerations
- A constructor has no return type—not even void.
- A default constructor appears only when no constructor is declared.
- Constructor chaining must ultimately reach superclass construction.
- Static methods have no instance
this. - Private fields do not replace invariant-preserving operations.
- Java passes object references by value.
- Reference assignment aliases an object; it does not copy it.
- An object array initially contains null references.
- Aggregation and composition are modeling distinctions, not keywords.
- Final references can still point to mutable objects.
Arrays of objects
Employee[] employees = new Employee[3];
This constructs one array containing three null references. It does not construct three Employee objects. Each employee must be created and assigned. Iterating and calling a method before filling every slot can produce NullPointerException.
Storing the same Employee reference in two slots creates aliases, not copies. A collection is often preferable when size changes dynamically.
Association, aggregation and composition
An association means objects know about or interact with one another. Aggregation is a whole-part relationship in which the part can exist independently. Composition expresses strong ownership in which the whole controls the part's meaningful lifetime.
public final class Employee {
private final Department department; // association/aggregation in this model
}
Several employees may refer to one independently existing department. Destroying an Employee does not logically destroy that Department.
A House creating and exclusively owning Room objects can be modeled as composition. Java has no aggregation or composition keyword; constructors, factories, visibility, mutation controls and lifecycle rules express the model.
Java is pass-by-value
static void replace(BankAccount account) {
account = new BankAccount("NEW", 0);
}
static void deposit(BankAccount account) {
account.deposit(100);
}
Each parameter receives a copy of the argument value. replace assigns a different reference to its local parameter and leaves the caller variable unchanged. deposit uses its copied reference to reach the same object, so the object's state changes. Java does not switch to pass-by-reference for objects.
Defensive copying
Defensive copying prevents callers from retaining a mutable path into internal representation.
public final class Schedule {
private final int[] slots;
public Schedule(int[] slots) { this.slots = slots.clone(); }
public int[] slots() { return slots.clone(); }
}
Clone is sufficient for an int array because components are primitive values. A nested array or collection of mutable elements requires copying at every level the ownership policy promises. Unmodifiable views prevent operations through one reference but may still reflect changes made through another reference to the backing collection.
Continue learning
Related notes
Put this topic into timed practice
Open mock tests when you want full-exam pacing, or keep drilling in practice mode.