An abstract class in Java is a class that is declared with the abstract keyword. An abstract class can contain abstract methods that do not have method bodies and must be implemented by subclasses. Abstract classes allow related classes to share common code, while forcing subclasses to provide implementations for key behaviors.
One aspect of abstract classes that sometimes causes confusion is how variables are handled. This article will provide a comprehensive, expert-level explanation of how variables work in Java abstract classes from the perspective of an experienced full-stack developer.
Access Modifiers on Variables
As stated in the Java language specification [1], abstract classes can declare instance, static, and local variables with any access modifier:
public– Accessible from any external classprotected– Accessible from subclasses and classes in the same packagedefault(no modifier) – Accessible from classes in the same packageprivate– Accessible only from within the abstract class
For example:
public abstract class Shape {
private int id; //private variable
int x; //package-private variable
protected float height; //accessible from subclasses
public double width; //fully public variable
}
Controlling access modifiers allows an abstract class designer to properly encapsulate state and behavior. According to Java design best practices [2], variables should default to the most restrictive modifier possible, elevating accessibility as needed.
Static vs Instance Variables
Like regular classes, abstract classes can declare both static and instance variables. The key difference, as noted in Oracle‘s Java tutorials [3], is that static variables are associated with the class itself, while instance variables are tied to individual object instances:
public abstract class MyClass {
private static int staticCount;
private int instanceCount;
public MyClass() {
staticCount++;
instanceCount++;
}
}
In this example, only one staticCount variable exists which counts all instances. But each MyClass instance will have its own instanceCount.
Understanding this distinction is important when designing abstract classes intended for extensive subclassing.
Arrays and Collections as Variables
Array and collection variables can also be declared normally in abstract classes:
import java.util.*;
public abstract class AbstractContainer {
private List<String> values;
protected int[] nums;
public AbstractContainer() {
values = new ArrayList<>();
nums = new int[10];
}
}
As shown in the Java tutorial on abstract classes [4], subclasses inherit and can utilize these variables:
public class StringContainer extends AbstractContainer {
public int getStringCount() {
return values.size(); //access inherited ArrayList
}
}
This allows sharing reusable data structures across class hierarchies.
Abstract Accessor Methods
A common pattern I often use as a full-stack developer is declaring abstract getter/setter methods for variables in abstract superclasses:
public abstract class BaseClass {
private String name;
public abstract String getName();
public abstract void setName(String name);
}
public class Person extends BaseClass {
@Override
public String getName() {
return name;
}
@Override
public void setName(String n) {
name = n;
}
}
This enforces subclasses to provide access logic for inheriting code while allowing flexibility in implementation details.
Variable Hiding with Redeclaration
An important aspect to understand is that subclasses can redeclare variables with the same name, effectively hiding superclass variables, according to the Java language spec [1]:
public abstract class Base {
public String name = "Base";
}
public class Sub extends Base {
public String name = "Sub";
}
Sub sub = new Sub();
sub.name; // Returns "Sub"
In most cases, this should be avoided for clarity. But can sometimes be useful for specialization purposes.
Overriding Methods to Reuse Variables
Even with inherited variables, subclasses can reuse variable values from the superclass by appropriately overriding methods:
public abstract class Shape {
protected String name;
public abstract String getName();
}
public class Circle extends Shape {
@Override
public String getName() {
return name + " Circle";
}
}
This allows reuse without duplication across the inheritance hierarchy.
Final Instance Variables
An abstract class can declare final instance variables which must be initialized in the constructor, similar to regular classes:
public abstract class Base {
private final String CREATOR;
public Base(String creator) {
this.CREATOR = creator;
}
public String getCreator() {
return CREATOR;
}
}
public class MyClass extends Base {
public MyClass() {
super("John");
}
}
However, as noted in StackOverflow [5], these cannot be accessing in subclasses trying to reuse them.
Differences from Interfaces
As a full-stack developer, I often consider using either abstract classes or Java interfaces for creating polymorphic behavior. The key difference as it pertains to variables is that interfaces only allow declaration of static final variables, while abstract classes have no restrictions [3].
So for state that needs to be shared across subclasses generally, abstract classes are more flexible. However, interfaces still play an important role in public APIs by defining types.
Java abstract classes have extensive support for declaring and leveraging all types of variables across class inheritance hierarchies, improving code reuse and polymorphic capabilities. Understanding the access modifiers, inheritance rules, and initialization timing helps ensure variables are used properly. Additionally, abstract getter/setter methods allow enforcing controlled access
There are many creative ways to leverage variables in abstract superclasses. Using an expert approach with careful encapsulation and design best practices will lead to clean, maintainable code architectures on Java projects.
References
[1] https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html[2] https://www.baeldung.com/java-best-practices-modifiers
[3] https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html
[4] https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html
[5] https://stackoverflow.com/questions/6850168/why-cant-a-concrete-sub-class-access-a-private-final-field-of-an-abstract-super


