Skip to content
IRC-CodingIRC-Coding
Static MethodsInstance AttributesClass RelationshipsGenericsUML

OOP Classes: Static vs Instance Methods & Attributes

Master OOP classes: static vs instance elements, class relationships, generics, UML patterns, and type-safe containers with examples.

S

schutzgeist

8 min read
OOP Classes: Static vs Instance Methods & Attributes

OOP Classes: Static vs Instance Methods, Attributes & Relationships

This guide covers core class components in object-oriented programming—including static versus instance elements, relationships between classes, and generics, with practical working examples.

In a Nutshell

Classes consist of attributes, methods, visibility modifiers, and contracts. Static elements belong to the class itself; instance elements belong to individual objects. Generics improve type safety and code reusability.

Core Concepts

A class defines structure and behavior through attributes, methods, visibility, constructors, invariants, and contracts.

Static vs Instance Elements:

Static Elements (Class-level)

  • Static attributes: Belong to the class, shared across all objects
  • Static methods: Can be called without creating an object
  • Common uses: Counters, caches, factories, constants
  • Access: Via the class name, not through an object

Instance Elements (Object-level)

  • Instance attributes: Each object maintains its own copy
  • Instance methods: Operate on an object’s state
  • Common uses: Object-specific data and behavior
  • Access: Only through an object instance

Class Relationships (UML):

  • Association: Simple relationship between classes
  • Aggregation: “has-a” relationship with independent parts
  • Composition: “is-part-of” relationship with dependent parts
  • Specialization: Subclass inherits from base class

Generics:

  • Type safety: Compile-time type checking
  • Reusability: Single code works with multiple types
  • Containers: List<T>, Map<K,V>, std::vector<T>

Key Points to Remember

  • Static attributes: Belong to the class; shared value across all instances
  • Instance attributes: Each object has its own copy
  • Static methods: Callable without an object; no access to this
  • Instance methods: Operate on object state via this
  • Class relationships: Association, aggregation, composition, inheritance
  • UML notation: Different symbols represent different relationship types
  • Generics: Type templates for type-safe containers
  • Professional relevance: Essential foundation for OOP development

Key Components

  1. Static attributes: Class variables for shared state
  2. Instance attributes: Object variables for individual state
  3. Static methods: Class methods independent of objects
  4. Instance methods: Object methods with state access
  5. Constructors: Object initialization
  6. Relationships: Structural connections between classes
  7. Generics: Type-parameterized classes and methods
  8. UML: Graphical notation for class design

Practical Examples

1. Static vs Instance Elements in Java

public class Employee {
    // Static attributes (belong to the class)
    private static int employeeCount = 0;
    private static final String COMPANY = "TechCorp";
    private static double minimumSalary = 2000.0;
    
    // Instance attributes (belong to each object)
    private int employeeId;
    private String name;
    private double salary;
    
    // Static initialization block
    static {
        System.out.println("Employee class loaded");
        employeeCount = 0;
    }
    
    // Constructor (instance initialization)
    public Employee(String name, double salary) {
        this.employeeId = ++employeeCount;
        this.name = name;
        this.salary = Math.max(salary, minimumSalary);
        
        System.out.println("Employee " + name + " created (ID: " + employeeId + ")");
    }
    
    // Static method (can be called without an object)
    public static int getEmployeeCount() {
        return employeeCount;
    }
    
    public static String getCompany() {
        return COMPANY;
    }
    
    public static void setMinimumSalary(double minimumSalary) {
        if (minimumSalary > 0) {
            Employee.minimumSalary = minimumSalary;
        }
    }
    
    // Instance method (requires an object)
    public void raiseSalary(double percent) {
        this.salary *= (1 + percent / 100);
        System.out.println(name + "'s salary raised to " + salary);
    }
    
    public void display() {
        System.out.println("ID: " + employeeId + ", Name: " + name + 
                          ", Salary: " + salary + ", Company: " + COMPANY);
    }
    
    // Getters for instance attributes
    public String getName() {
        return name;
    }
    
    public double getSalary() {
        return salary;
    }
}

// Using the class
public class EmployeeDemo {
    public static void main(String[] args) {
        // Call static methods (without an object)
        System.out.println("Employee count: " + Employee.getEmployeeCount());
        System.out.println("Company: " + Employee.getCompany());
        
        Employee.setMinimumSalary(2500.0);
        
        // Create objects (instances)
        Employee alice = new Employee("Alice", 3000.0);
        Employee bob = new Employee("Bob", 2800.0);
        
        // Call instance methods
        alice.raiseSalary(5.0);
        bob.display();
        
        // Static method after object creation
        System.out.println("Employee count: " + Employee.getEmployeeCount());
        
        // Error: this is not available in static methods
        // public static void errorMethod() {
        //     System.out.println(this.name); // Error: cannot access this
        // }
    }
}

2. Class Relationships with UML Examples

// Association: Instructor teaches courses
public class Instructor {
    private String name;
    private List<Course> taughtCourses = new ArrayList<>();
    
    public Instructor(String name) {
        this.name = name;
    }
    
    public void addCourse(Course course) {
        taughtCourses.add(course);
        course.setInstructor(this); // Back reference
    }
    
    public void showCourses() {
        System.out.println(name + " teaches:");
        for (Course course : taughtCourses) {
            System.out.println("  - " + course.getTitle());
        }
    }
}

public class Course {
    private String title;
    private Instructor instructor; // Back reference
    
    public Course(String title) {
        this.title = title;
    }
    
    public void setInstructor(Instructor instructor) {
        this.instructor = instructor;
    }
    
    public String getTitle() {
        return title;
    }
}

// Aggregation: Department has employees (employees can exist without the department)
public class Department {
    private String name;
    private List<Employee> employees = new ArrayList<>();
    
    public Department(String name) {
        this.name = name;
    }
    
    public void addEmployee(Employee employee) {
        this.employees.add(employee);
    }
    
    public void removeEmployee(Employee employee) {
        this.employees.remove(employee);
        // Employee continues to exist
    }
}

// Composition: Order contains order items (items exist only as part of the order)
public class Order {
    private String orderId;
    private List<OrderItem> items = new ArrayList<>();
    
    public Order(String orderId) {
        this.orderId = orderId;
    }
    
    public void addItem(String product, int quantity, double price) {
        OrderItem item = new OrderItem(product, quantity, price);
        items.add(item);
    }
    
    public double calculateTotal() {
        return items.stream()
            .mapToDouble(OrderItem::getTotalPrice)
            .sum();
    }
    
    // Inner class for composition
    private class OrderItem {
        private String product;
        private int quantity;
        private double unitPrice;
        
        public OrderItem(String product, int quantity, double unitPrice) {
            this.product = product;
            this.quantity = quantity;
            this.unitPrice = unitPrice;
        }
        
        public double getTotalPrice() {
            return quantity * unitPrice;
        }
    }
}

3. Generics with Static Members

// Generic class with static members
public class Container<T> {
    // Static attributes (not generic!)
    private static int containerCount = 0;
    private static final String VERSION = "1.0";
    
    // Instance attributes (generic)
    private T content;
    private int id;
    
    public Container(T content) {
        this.content = content;
        this.id = ++containerCount;
    }
    
    // Static method (cannot access T)
    public static int getContainerCount() {
        return containerCount;
    }
    
    public static String getVersion() {
        return VERSION;
    }
    
    // Instance method (can access T)
    public T getContent() {
        return content;
    }
    
    public void setContent(T content) {
        this.content = content;
    }
    
    public void display() {
        System.out.println("Container #" + id + ": " + 
                          (content != null ? content.toString() : "empty"));
    }
    
    // Generic static method
    public static <U> Container<U> create(U content) {
        return new Container<>(content);
    }
}

// Usage
public class ContainerDemo {
    public static void main(String[] args) {
        // Call static methods
        System.out.println("Container version: " + Container.getVersion());
        
        // Create different container types
        Container<String> stringContainer = new Container<>("Hello");
        Container<Integer> intContainer = new Container<>(42);
        Container<Double> doubleContainer = Container.create(3.14);
        
        stringContainer.display();
        intContainer.display();
        doubleContainer.display();
        
        System.out.println("Container count: " + Container.getContainerCount());
        
        // Error: Static attributes are not generic
        // Container<String>.getContainerCount(); // Syntax error
    }
}

4. Factory Pattern with Static Methods

public class VehicleFactory {
    // Static factory methods
    public static Vehicle createCar(String brand, int power) {
        return new Car(brand, power, 4);
    }
    
    public static Vehicle createMotorcycle(String brand, int power) {
        return new Motorcycle(brand, power, false);
    }
    
    public static Vehicle createTruck(String brand, int power, double load) {
        return new Truck(brand, power, load);
    }
    
    // Static method with validation
    public static Vehicle createVehicle(String type, String brand, int power) {
        switch (type.toLowerCase()) {
            case "car":
                return createCar(brand, power);
            case "motorcycle":
                return createMotorcycle(brand, power);
            case "truck":
                return createTruck(brand, power, 1000.0);
            default:
                throw new IllegalArgumentException("Unknown vehicle type: " + type);
        }
    }
}

// Abstract base class
abstract class Vehicle {
    protected String brand;
    protected int power;
    
    public Vehicle(String brand, int power) {
        this.brand = brand;
        this.power = power;
    }
    
    public abstract void display();
}

// Concrete classes
class Car extends Vehicle {
    private int doors;
    
    public Car(String brand, int power, int doors) {
        super(brand, power);
        this.doors = doors;
    }
    
    @Override
    public void display() {
        System.out.println("Car: " + brand + ", " + power + " HP, " + doors + " doors");
    }
}

class Motorcycle extends Vehicle {
    private boolean hasSidecar;
    
    public Motorcycle(String brand, int power, boolean hasSidecar) {
        super(brand, power);
        this.hasSidecar = hasSidecar;
    }
    
    @Override
    public void display() {
        System.out.println("Motorcycle: " + brand + ", " + power + " HP, " + 
                          (hasSidecar ? "with" : "without") + " sidecar");
    }
}

class Truck extends Vehicle {
    private double load;
    
    public Truck(String brand, int power, double load) {
        super(brand, power);
        this.load = load;
    }
    
    @Override
    public void display() {
        System.out.println("Truck: " + brand + ", " + power + " HP, " + load + " kg load");
    }
}

// Factory usage
public class FactoryDemo {
    public static void main(String[] args) {
        // Use static factory methods
        Vehicle golf = VehicleFactory.createCar("Volkswagen", 110);
        Vehicle harley = VehicleFactory.createMotorcycle("Harley", 80);
        Vehicle scania = VehicleFactory.createTruck("Scania", 500, 20000.0);
        
        golf.display();
        harley.display();
        scania.display();
        
        // Dynamic creation
        Vehicle bmw = VehicleFactory.createVehicle("car", "BMW", 150);
        bmw.display();
    }
}

UML Notation for Class Members

Class with Static and Instance Members

+---------------------------+
|        Employee           |
+---------------------------+
| - employeeCount: int     |  <<static>>
| - COMPANY: String        |  <<static>>
| - employeeId: int        |
| - name: String           |
| - salary: double         |
+---------------------------+
| + getEmployeeCount(): int | <<static>>
| + setMinimumSalary(double): void | <<static>>
| + raiseSalary(double): void      |
| + display(): void                |
+---------------------------+

Relationships in UML

Lecturer 1..* --* Course           (Association)
Department 1 --o* Employee         (Aggregation)
Order 1 --* OrderItem              (Composition)
Vehicle <|-- Car                   (Inheritance)

Static vs Instance: Decision Guide

When to Use Static Elements

Static Attributes:

  • Counters across all instances
  • Class-wide constants
  • Shared resources (database connection)
  • Class-level caches

Static Methods:

  • Factory methods for object creation
  • Utility methods without state
  • Conversion methods
  • Validation methods

When to Use Instance Elements

Instance Attributes:

  • Object-specific data
  • State that changes per object
  • Per-object configuration

Instance Methods:

  • Methods that access object state
  • Behavior that depends on instance data
  • Methods that need the this reference

Advantages and Disadvantages

Advantages of Static Elements

  • Memory efficiency: Only one copy across all objects
  • Easy access: Callable without object instantiation
  • Shared state: Consistent across all objects
  • Factory Pattern: Simplified object creation

Disadvantages

  • Global state: Harder to test
  • Thread safety: Issues with concurrent access
  • Flexibility: No polymorphism possible
  • Initialization: Complex dependencies

Common Exam Questions

  1. What’s the difference between static and instance attributes? Static attributes belong to the class (one copy), while instance attributes belong to each object (one per instance).

  2. Can static methods access instance attributes? No, because they lack a this reference and don’t know which object they belong to.

  3. Explain aggregation vs composition! In aggregation, parts can exist without the whole. In composition, parts exist only as part of the whole.

  4. Why aren’t static attributes generic? They belong to the class, not instances, so there’s only one version per class.

Key Resources

  1. https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
  2. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members
  3. https://www.uml-diagrams.org/class-diagrams.html

Continue Your OOP Learning Path

You’ve now completed all OOP articles. Return to the first article: Object-Oriented Programming OOP Fundamentals.

Back to Blog
Share:

Related Posts