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:

  1. Non-static nested (inner) class — associated with an instance, can access outer instance members.
  2. Static nested class — like a top-level class but nested; cannot access non-static members of outer without an instance.
  3. Local class — defined inside a method.
  4. 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:

FeatureDescription
Private ConstructorPrevents 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 InitializationThe instance is created only when getInstance() is first called.
Thread SafetyGuaranteed 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:

FeatureDescription
Enum TypeJava guarantees that each enum constant is instantiated only once.
Thread SafetyEnum instances are inherently thread-safe.
Serialization SafetyEnums 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:

FeatureInitialization-on-Demand HolderEnum 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.
Scroll to Top