Java Class Features
Java is an object-oriented programming language where classes form the backbone of its structure. Understanding the key features of a Java class is essential for writing clean, maintainable, and efficient code. This post explores the most important aspects of Java classes — from attributes and methods to constructors, access control, and more.
We’ll cover how fields (instance and static) store object state, how methods (instance and static) define behavior, and how variable scope governs visibility within the class. You’ll also learn how constructors initialize objects, how access modifiers protect data, and how initializer blocks assist in complex setups.
We dive into advanced topics such as interfaces for abstraction, packages for organizing code, inner and static nested classes for structural grouping, anonymous classes for quick customization, and singleton patterns for instance control. We wrap up with wrapper classes, enums, and best practices that will help you write robust and modern Java code.
Whether you’re a beginner aiming to master class structures or a developer brushing up on fundamentals, this guide will give you a comprehensive overview of everything that makes a Java class powerful.
Class Attributes
What they are: Class attributes (also called fields or instance variables) hold object state. There are two broad kinds:
- Instance fields — belong to each object instance.
- Static (class) fields — belong to the class itself, shared across all instances.
Key points:
- Default values: numeric→0, boolean→false, reference→null.
- Visibility controlled by access modifiers (private, protected, public, package-private).
- Prefer encapsulation: private fields + public getters/setters.
Example
public class Person {
// instance fields
private String name;
private int age;
// static field (class attribute)
public static final String SPECIES = “Homo sapiens”;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getters & setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
Class Methods
What they are: Methods define behavior. Two main kinds:
- Instance methods — operate on instance data, need an object to be called.
- Static methods — belong to class, can be called without instance.
Key points:
- Use static for utility behavior.
- Overloading: same method name, different parameters.
- Overriding: subclass provides specific implementation.
Example
public class Calculator {
// static method
public static int add(int a, int b) {
return a + b;
}
// instance method
public int multiply(int a, int b) {
return a * b;
}
}
Usage:
int sum = Calculator.add(2,3); // static call
Calculator calc = new Calculator();
int prod = calc.multiply(2,3); // instance call
Variable Scopes
What they are: Scope determines where a variable is visible.
- Local variables — declared in method/block; exist only within it.
- Instance variables (fields) — visible across instance methods; lifetime = object.
- Static variables — visible to all static and instance methods of the class.
- Method parameters — local to method.
Key rules:
- Local variables must be initialized before use.
- Shadowing: local vars can hide fields (use this to reference field).
Example
public class ScopeExample {
private int x = 10; // instance
public void exampleMethod(int x) { // parameter shadows field
int y = 5; // local
System.out.println(“local x (param): ” + x);
System.out.println(“field x: ” + this.x);
System.out.println(“local y: ” + y);
}
}
Constructors
What they are: Special methods that initialize new objects. Name matches class and has no return type.
Types:
- Default constructor — provided if none declared.
- No-arg constructor — explicitly declared with no parameters.
- Parameterized constructor — takes parameters.
- Constructor chaining — call another constructor using this(…).
Example
public class Book {
private String title;
private String author;
// no-arg constructor
public Book() {
this(“Unknown”, “Unknown”);
}
// parameterized constructor
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
Access Modifiers
Four levels:
- public — visible everywhere.
- protected — visible in same package and subclasses.
- package-private (no modifier) — visible only within same package.
- private — visible only within the class.
Example
package com.example;
public class AccessDemo {
public int pub = 1;
protected int prot = 2;
int pack = 3; // package-private
private int priv = 4;
}
Tip: Favor private fields and public methods to expose behavior.
Initializer Block
What they are: Blocks that run when an object is created.
- Instance initializer block: executed each time an instance is created (runs before constructor body).
- Static initializer block: executed once when class is loaded.
Use-cases: Complex initialization shared across constructors, initializing static resources.
Example
public class InitExample {
static {
System.out.println(“Static initializer: class loaded.”);
}
{
System.out.println(“Instance initializer: before constructor.”);
}
public InitExample() {
System.out.println(“Constructor body.”);
}
public static void main(String[] args) {
new InitExample();
new InitExample();
}
}
Output:
Static initializer: class loaded.
Instance initializer: before constructor.
Constructor body.
Instance initializer: before constructor.
Constructor body.
Interfaces
What they are: Contract defining method signatures (and since Java 8, default and static methods; since Java 9, private methods). A class implements interfaces.
Key points:
- Interfaces support multiple inheritance of types.
- Methods are public abstract by default (unless default/static).
- Fields in interfaces are public static final by default.
Example
public interface Drivable {
void accelerate(int increment); // abstract
default void honk() {
System.out.println(“Beep!”);
}
static void serviceCheck() {
System.out.println(“Service check done.”);
}
}
public class Car implements Drivable {
private int speed = 0;
@Override
public void accelerate(int increment) {
speed += increment;
System.out.println(“Speed: ” + speed);
}
}
Packages
What they are: Namespaces for organizing classes and controlling access. Package statement must be first line (except comments).
Benefits:
- Avoid name clashes.
- Access control (package-private).
- Easier project structure.
Example
// file: src/com/example/util/Utils.java
package com.example.util;
public class Utils {
public static String greet(String name) { return “Hello, ” + name; }
}
Usage from another package:
import com.example.util.Utils;
System.out.println(Utils.greet(“Alice”));
Inner Classes
Kinds:
- Non-static nested (inner) class — associated with an instance, can access outer instance members.
- Static nested class — like a top-level class but nested; cannot access non-static members of outer without an instance.
- Local class — defined inside a method.
- Anonymous class — expression to create an instance of an unnamed class (covered later).
Example (non-static and static nested)
public class Outer {
private int outerValue = 10;
// non-static inner class
public class Inner {
public void print() {
System.out.println(“outerValue = ” + outerValue);
}
}
// static nested class
public static class StaticNested {
public void show() {
System.out.println(“I am static nested”);
}
}
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner inner = o.new Inner();
inner.print();
Outer.StaticNested nested = new Outer.StaticNested();
nested.show();
}
}
Static Classes
Clarification: Java doesn’t have “static classes” at top level. Only nested classes can be static (static nested classes). They don’t hold implicit reference to outer instance.
When to use: When grouping helper classes that don’t need outer instance.
(See static nested example above.)
Anonymous Classes
What they are: Inline class definitions without a name, used for single-use specializations of interfaces or classes.
Use-cases: Quick event handlers, comparators, small overrides — though lambdas often replace interfaces that are functional.
Example
import java.util.Comparator;
import java.util.Arrays;
public class AnonymousExample {
public static void main(String[] args) {
String[] arr = {“banana”, “apple”, “cherry”};
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a); // reverse order
}
});
System.out.println(Arrays.toString(arr));
}
}
With Java 8+, the same comparator can use a lambda:
Arrays.sort(arr, (a, b) -> b.compareTo(a));
Singleton Class
What it is: A class that allows only one instance in the JVM.
Common implementations:
- Eager initialization
- Lazy initialization with synchronized
- Initialization-on-demand holder idiom (recommended)
- Enum-based singleton (most robust)
Example — Initialization-on-demand holder
public class Singleton {
private Singleton() { }
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
Enum singleton example
public enum EnumSingleton {
INSTANCE;
public void doSomething() { System.out.println(“Doing…”); }
}
Enum approach handles serialization and reflection safely.
Singleton Class – Explanation
Let’s break down and explain both examples — the Initialization-on-Demand Holder Singleton and the Enum Singleton — so you can clearly understand how and why they work.
1. Singleton with Initialization-on-Demand Holder Idiom
Explanation:
This is a thread-safe, lazy-loaded singleton pattern without synchronization overhead.
Key Concepts:
Feature | Description |
---|---|
Private Constructor | Prevents external instantiation — only the class itself can create the instance. |
Static Inner Class (Holder ) | Holds the singleton instance. It is not loaded until the outer class calls getInstance() . |
Lazy Initialization | The instance is created only when getInstance() is first called. |
Thread Safety | Guaranteed by Java’s class loading mechanism: the JVM loads the Holder class only once and safely, even in multithreaded contexts. |
Flow:
- Singleton.getInstance() is called.
- JVM loads the inner Holder class.
- The static field INSTANCE is initialized once, thread-safely.
Returns the Singleton instance.
Why Use This?
- No explicit synchronization needed.
- Efficient and elegant.
- Recommended in most modern Java applications.
2. Enum Singleton
Explanation:
This is the simplest and most robust singleton pattern in Java.
Key Concepts:
Feature | Description |
---|---|
Enum Type | Java guarantees that each enum constant is instantiated only once. |
Thread Safety | Enum instances are inherently thread-safe. |
Serialization Safety | Enums are safe from serialization attacks that can create new instances. |
Reflection Safety | Enums cannot be instantiated using reflection, unlike regular classes. |
Usage:
EnumSingleton.INSTANCE.doSomething();
Why Use This?
- Short, simple, and built-in protection against common pitfalls (e.g., serialization, reflection).
- Best choice if the singleton doesn’t need lazy initialization or complex logic.
Summary Comparison:
Feature | Initialization-on-Demand Holder | Enum Singleton |
---|---|---|
Thread-safe | ✅ Yes | ✅ Yes |
Lazy-loaded | ✅ Yes | ❌ No (eager by default) |
Simple to implement | 🟡 Medium | ✅ Very simple |
Safe from reflection hacks | ❌ No | ✅ Yes |
Serialization safe | ❌ No (needs readResolve ) | ✅ Yes |
Best for complex logic? | ✅ Yes | ❌ No (less flexible) |
Final Recommendation:
Use Enum Singleton when you want a simple and safe singleton (no lazy init required).
Use Initialization-on-Demand Holder when you need lazy initialization and more control (e.g., extending interfaces, adding complex setup).
Wrapper Classes
What they are: Object representations of primitive types (Integer, Double, Boolean, etc.).
Why use them:
- Collections (generics) work with objects only.
- Utility methods (e.g., Integer.parseInt).
Autoboxing/unboxing: Java auto-converts between primitives and wrappers since Java 5.
Example
Integer a = 10; // autoboxing
int b = a + 5; // unboxing
List<Integer> list = new ArrayList<>();
list.add(20); // works because of wrapper
Enum Class
What it is: A type-safe set of constants. Enums are full-featured classes: can have fields, methods, and constructors.
Example
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Advanced enum with fields and methods
public enum TrafficLight {
RED(30), YELLOW(5), GREEN(25);
private int durationSeconds;
TrafficLight(int durationSeconds) {
this.durationSeconds = durationSeconds;
}
public int getDuration() { return durationSeconds; }
}
Enum Constructor
Rules:
- Enum constructors are private (or package-private) by design — you cannot use public.
- Instantiation occurs automatically for each constant.
Example (continuation from TrafficLight above):
TrafficLight t = TrafficLight.RED;
System.out.println(“Red lasts ” + t.getDuration() + ” seconds.”);
Enum String
Meaning/usage: Converting enums to Strings and back.
- enumInstance.name() returns the identifier as declared.
- enumInstance.toString() returns a string; can be overridden.
- Enum.valueOf(EnumType.class, “CONSTANT”) to convert String to enum (throws IllegalArgumentException if mismatch).
- Safer conversion methods: use try-catch or map by custom string field.
Example
public enum Level {
LOW(“Low priority”),
MEDIUM(“Medium priority”),
HIGH(“High priority”);
private String label;
Level(String label) { this.label = label; }
@Override public String toString() { return label; }
public String nameAsDeclared() { return name(); }
}
// convert back
Level l = Level.valueOf(“LOW”); // by declared name
System.out.println(Level.HIGH); // prints “High priority” (toString)
Robust parsing from arbitrary string
public static Level fromLabel(String label) {
for (Level lv : Level.values())
if (lv.label.equalsIgnoreCase(label)) return lv;
throw new IllegalArgumentException(“Unknown label: ” + label);
}
Quick tips & best practices (summary)
- Use private fields + public getters/setters for encapsulation.
- Prefer composition over inheritance.
- Use interfaces for contracts and multiple type inheritance.
- Use enums (or enum singletons) for fixed sets of constants and singletons where safe instantiation matters.
- Prefer initialization-on-demand holder for singletons or the enum approach for safety.
- Keep nested static classes when they don’t need an outer instance.
- Avoid large anonymous classes; use lambdas where applicable.