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
- Static attributes: Class variables for shared state
- Instance attributes: Object variables for individual state
- Static methods: Class methods independent of objects
- Instance methods: Object methods with state access
- Constructors: Object initialization
- Relationships: Structural connections between classes
- Generics: Type-parameterized classes and methods
- 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
thisreference
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
-
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).
-
Can static methods access instance attributes? No, because they lack a
thisreference and don’t know which object they belong to. -
Explain aggregation vs composition! In aggregation, parts can exist without the whole. In composition, parts exist only as part of the whole.
-
Why aren’t static attributes generic? They belong to the class, not instances, so there’s only one version per class.
Key Resources
- https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members
- 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.



