Java: Can You Override Static Variables from a Parent Class? A Practical Guide with BaseModel Example

In Java, object-oriented programming (OOP) revolves around concepts like inheritance, polymorphism, and encapsulation. One common question that arises when working with inheritance is: Can you override static variables from a parent class? This is especially relevant for developers building reusable components (e.g., ORM models, utility classes) where static variables might store class-level metadata (e.g., database table names).

In this blog, we’ll demystify this topic by exploring static variables, method overriding, and why static variables behave differently. We’ll use a practical example with a BaseModel class (a common pattern in ORMs) and its child classes to illustrate key concepts. By the end, you’ll understand why static variables can’t be overridden, how they interact with inheritance, and best practices for using them.

Table of Contents#

  1. Understanding Static Variables in Java
  2. What is Method Overriding?
  3. Can You Override Static Variables? Let’s Find Out
  4. Practical Example: BaseModel and Child Classes
  5. Key Takeaways
  6. Common Misconceptions
  7. Conclusion
  8. References

Understanding Static Variables in Java#

Before diving into overriding, let’s clarify what static variables are.

Static variables (also called class variables) are declared with the static keyword. Unlike instance variables (which are unique to each object), static variables belong to the class itself, not individual instances. They are initialized once when the class is loaded into memory and shared across all instances of the class.

Key Characteristics of Static Variables:#

  • Shared across all instances of the class.
  • Initialized when the class is loaded (before any instances are created).
  • Accessed using the class name (e.g., ClassName.variableName) rather than an instance (though instance access is allowed but discouraged).

Example:

public class Counter {
    public static int count = 0; // Static variable
 
    public Counter() {
        count++; // Incremented every time an instance is created
    }
}

Here, count is shared across all Counter instances.

What is Method Overriding?#

Method overriding is a core OOP concept where a subclass provides a specific implementation of a method that is already defined in its parent class. For overriding to work:

  • The method must have the same name, return type, and parameters (method signature).
  • The subclass method must not reduce the access visibility of the parent method (e.g., a public parent method can’t be overridden as private).
  • Overridden methods are resolved at runtime based on the actual object type (polymorphism).

Example of Method Overriding:

class Animal {
    public void makeSound() {
        System.out.println("Generic animal sound");
    }
}
 
class Dog extends Animal {
    @Override // Explicit annotation to enforce overriding
    public void makeSound() {
        System.out.println("Bark");
    }
}
 
public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // Polymorphic reference
        animal.makeSound(); // Output: "Bark" (runtime resolution)
    }
}

Here, Dog overrides makeSound(), and the runtime object type (Dog) determines which method is called.

Can You Override Static Variables? Let’s Find Out#

The short answer: No, static variables cannot be overridden in Java.

Why Not?#

Static variables are bound to the class, not individual objects. Overriding relies on dynamic (runtime) resolution based on the object’s type, but static members are resolved at compile time based on the reference type (the class of the variable used to access them).

Shadowing: The Closest Alternative#

If a subclass declares a static variable with the same name as a static variable in its parent class, it does not override the parent’s variable. Instead, it shadows (hides) the parent’s variable. Shadowing means the subclass’s variable is used when accessed through the subclass, and the parent’s variable is used when accessed through the parent.

Key Differences Between Overriding and Shadowing:#

OverridingShadowing
Applies to instance methods.Applies to static variables (and static methods).
Resolved at runtime (object type).Resolved at compile time (reference type).
Uses @Override annotation.No @Override (not applicable).

Practical Example: BaseModel and Child Classes#

Let’s use a BaseModel class (common in ORMs like Hibernate) to demonstrate static variable behavior. Suppose BaseModel defines a static tableName variable, and child classes like User and Product want to "override" this to specify their database table names.

Step 1: Define the BaseModel Class#

public class BaseModel {
    // Static variable: Represents the database table name for the model
    public static String tableName = "base_model";
 
    // Static method to get the table name
    public static String getTableName() {
        return tableName;
    }
}

Step 2: Define Child Classes (User and Product)#

Child classes will declare their own tableName static variable, shadowing the parent’s tableName.

// User class extending BaseModel
public class User extends BaseModel {
    // Shadowing the parent's static variable
    public static String tableName = "users";
}
 
// Product class extending BaseModel
public class Product extends BaseModel {
    // Shadowing the parent's static variable
    public static String tableName = "products";
}

Step 3: Test Access to Static Variables#

Let’s access tableName through class names, instances, and polymorphic references to see the behavior.

public class Main {
    public static void main(String[] args) {
        // Case 1: Access via class name (RECOMMENDED)
        System.out.println("BaseModel.tableName: " + BaseModel.tableName); // "base_model"
        System.out.println("User.tableName: " + User.tableName);         // "users"
        System.out.println("Product.tableName: " + Product.tableName);   // "products"
 
        // Case 2: Access via instance (NOT RECOMMENDED, but illustrative)
        BaseModel baseModel = new BaseModel();
        User user = new User();
        Product product = new Product();
 
        System.out.println("\nInstance access:");
        System.out.println("baseModel.tableName: " + baseModel.tableName); // "base_model" (reference type: BaseModel)
        System.out.println("user.tableName: " + user.tableName);           // "users" (reference type: User)
        System.out.println("product.tableName: " + product.tableName);     // "products" (reference type: Product)
 
        // Case 3: Polymorphic reference (Child instance assigned to Parent variable)
        BaseModel polymorphicUser = new User(); // Object is User, reference is BaseModel
        System.out.println("\nPolymorphic reference:");
        System.out.println("polymorphicUser.tableName: " + polymorphicUser.tableName); // "base_model" (reference type: BaseModel)
 
        // Case 4: Access static method (behaves the same as static variables)
        System.out.println("\nStatic method access:");
        System.out.println("BaseModel.getTableName(): " + BaseModel.getTableName()); // "base_model"
        System.out.println("User.getTableName(): " + User.getTableName());         // "users" (uses User's tableName)
        System.out.println("polymorphicUser.getTableName(): " + polymorphicUser.getTableName()); // "base_model" (reference type: BaseModel)
    }
}

Output Explanation:#

BaseModel.tableName: base_model
User.tableName: users
Product.tableName: products

Instance access:
baseModel.tableName: base_model
user.tableName: users
product.tableName: products

Polymorphic reference:
polymorphicUser.tableName: base_model

Static method access:
BaseModel.getTableName(): base_model
User.getTableName(): users
polymorphicUser.getTableName(): base_model
  • Class Name Access: Always uses the static variable from the class specified (e.g., User.tableName uses User’s tableName).
  • Instance Access: Uses the static variable from the reference type (the class of the variable), not the actual object type. For polymorphicUser (a User object referenced as BaseModel), tableName resolves to BaseModel’s value.
  • Static Methods: Behave identically to static variables—they are resolved at compile time based on the reference type, not the object type.

Key Takeaways#

  1. Static Variables Cannot Be Overridden: They belong to the class, not objects, so overriding (runtime polymorphism) does not apply.
  2. Shadowing ≠ Overriding: A subclass with a static variable同名静态变量 shadows the parent’s variable, but this is not overriding. Shadowing is resolved at compile time.
  3. Access Static Variables via Class Names: Using ClassName.variableName is clearer and avoids confusion. Instance access (instance.variableName) is allowed but misleading (it uses the reference type, not the object).
  4. Static Methods Also Cannot Be Overridden: Like static variables, static methods are class-bound and resolve based on the reference type. They can be shadowed but not overridden.

Common Misconceptions#

  • "Shadowing is Overriding": No. Overriding enables runtime polymorphism; shadowing does not. The value of a shadowed static variable depends on the reference type, not the object.
  • "Using an Instance Changes the Static Variable": No. Accessing a static variable via an instance (e.g., user.tableName) is syntactic sugar for User.tableName. The instance is irrelevant.
  • "Casting to Parent Changes the Static Variable": No. Casting a child instance to a parent reference (e.g., BaseModel b = new User()) still uses the parent’s static variable, as the reference type is BaseModel.

Conclusion#

Static variables in Java are class-level members, so they cannot be overridden. Instead, subclasses may shadow parent static variables, but this behavior is distinct from overriding and is resolved at compile time based on the reference type.

When working with inheritance and static variables (e.g., in ORM models like BaseModel), always access static variables via the class name to avoid confusion. Remember: static = class-bound, instance = object-bound.

By understanding these nuances, you’ll write clearer, less error-prone Java code.

References#