Loading content...
Every object in an object-oriented system has a memory—a collection of data that defines what the object is at any given moment. This memory is captured through attributes, the variables that live within an object and hold its state. Understanding attributes deeply is fundamental to mastering object-oriented design.
When you look at a real-world entity—a bank account, a shopping cart, a user profile—you instinctively recognize that it has properties. A bank account has a balance, an account number, and an owner. A shopping cart has items and a total. These properties, when translated into code, become attributes—the fundamental units of object state.
By the end of this page, you will understand attributes as the carriers of object state, know how to identify appropriate attributes for your classes, distinguish between different types of attributes, and recognize the critical relationship between attributes and object identity.
State is one of the three pillars that define an object, alongside identity and behavior. An object's state is the collection of all its attribute values at a particular moment in time. This state can change as the object interacts with other objects and responds to events.
Consider a simple example: a BankAccount object. Its state might include:
At any instant, the combination of these values represents the complete state of that particular bank account. Change any one of them, and you have a different state—though the object's identity remains the same.
A crucial distinction: an object's identity persists even as its state changes. A bank account that starts with $100 and grows to $1000 is still the same bank account. The state has changed, but the identity has not. This distinction becomes especially important when we discuss object equality and hashing.
Why State Matters:
State is what gives objects meaning beyond mere code structure. A method that operates on state-less data is essentially a function in disguise. True object-orientation emerges when:
This is fundamentally different from procedural programming, where data structures and the functions that operate on them are separate entities.
An attribute (also called a field, instance variable, or member variable depending on the language) is a variable that is declared within a class and exists for the lifetime of each object created from that class. Every instance of the class has its own copy of each attribute, with its own values.
The Technical Definition:
Attributes are variables that:
12345678910111213
public class BankAccount { // These are attributes - they define the state each account object will hold private String accountNumber; // Unique identifier for this account private String accountHolder; // Name of the person who owns this account private double balance; // Current monetary balance private boolean isActive; // Whether the account is currently active private LocalDateTime openedOn; // When the account was created // Each instance of BankAccount will have its own copy of these variables // with its own values. Two different BankAccount objects will have // two different accountNumber values, two different balance values, etc.}Key Characteristics of Attributes:
1. Instance-Specific Storage
Unlike local variables (which exist only within a method call) or class-level static variables (which are shared across all instances), attributes provide storage that is:
2. Type Safety
Attributes have declared types that enforce what values can be stored. A balance declared as double cannot accidentally hold a String. This compile-time type checking prevents entire categories of bugs.
3. Scope and Lifetime
The scope of an attribute is the entire class body—any method can access it. The lifetime extends from object construction to object destruction. This gives attributes a fundamentally different character from local variables.
Attributes should be named as nouns or noun phrases that clearly describe what they represent. Use 'balance' not 'bal', 'accountHolder' not 'ah', 'isActive' not 'flag1'. Well-named attributes serve as documentation and make code self-explanatory.
Attributes can be classified along several dimensions. Understanding these classifications helps you make better design decisions about what attributes your classes need and how they should behave.
Mutable vs Immutable Attributes:
A mutable attribute can change its value after the object is created. The balance of a bank account is mutable—it changes with deposits and withdrawals.
An immutable attribute's value is set at creation and never changes. The accountNumber is typically immutable—once assigned, it remains constant for the life of the account.
Design insight: Prefer immutability where possible. Immutable attributes:
1234567891011121314151617181920212223
public class BankAccount { // Immutable attribute - set once, never changes // The 'final' keyword enforces immutability at compile time private final String accountNumber; // Mutable attribute - can change throughout object's lifetime private double balance; public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; // Can only be set here this.balance = initialBalance; // Can be changed later } // accountNumber has no setter - it's truly immutable public String getAccountNumber() { return accountNumber; } // balance can change via controlled operations public void deposit(double amount) { this.balance += amount; // State mutation happens here }}Intrinsic vs Derived Attributes:
An intrinsic (or stored) attribute holds a value directly. The balance is intrinsic—when you call getBalance(), you return the stored value.
A derived (or computed) attribute's value is calculated from other attributes. Consider netWorth that might be calculated as assets - liabilities. Derived attributes aren't stored; they're computed on demand.
Design considerations for derived attributes:
A common mistake is storing derived values as regular attributes and manually keeping them synchronized. This creates invariant maintenance burden and bugs when synchronization is forgotten. Unless caching is necessary for performance, prefer computing derived values on demand.
This distinction is fundamental and often misunderstood by developers new to OOP.
Instance Attributes:
Static (Class) Attributes:
| Aspect | Instance Attribute | Static Attribute |
|---|---|---|
| Storage | One per object | One per class (shared) |
| Access | object.attribute | ClassName.attribute |
| Purpose | Object-specific state | Class-wide state |
| Initialization | In constructor | At class loading or static initializer |
| Memory | Scales with objects | Fixed regardless of object count |
| Example | account.balance | BankAccount.interestRate |
123456789101112131415161718192021222324252627282930
public class BankAccount { // Static attribute - shared across ALL bank accounts // If the interest rate changes, it changes for everyone private static double interestRate = 0.02; // 2% // Static counter - tracks how many accounts have ever been created private static int totalAccountsCreated = 0; // Instance attributes - unique to each account private final String accountNumber; private double balance; public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; this.balance = initialBalance; totalAccountsCreated++; // Increment class-level counter } // Instance method using static attribute public double calculateYearlyInterest() { // Each account calculates based on its own balance // but uses the shared interest rate return this.balance * interestRate; } // Static method to adjust rate for all accounts public static void setInterestRate(double newRate) { interestRate = newRate; // Affects all future interest calculations }}Use static attributes sparingly and only when the value truly belongs to the class rather than individual objects. Good uses: constants (like Math.PI), counters for instances created, shared configuration values, cached expensive computations. Bad uses: anything that should vary per object, mutable shared state in multi-threaded contexts (dangerous!).
The type of an attribute—whether it holds a primitive value or a reference to another object—has significant design implications.
Primitive Attributes:
Hold simple values directly: int, double, boolean, char, etc. These are:
Reference Attributes:
Hold references to other objects. These are:
1234567891011121314151617
public class Order { // Primitive attributes private int orderId; // Stores the actual number private double totalAmount; // Stores the actual decimal value private boolean isPaid; // Stores true or false directly // Reference attributes private Customer customer; // Stores a reference to a Customer object private Address shippingAddress; // Reference to an Address object private List<OrderItem> items; // Reference to a list of OrderItem objects private LocalDateTime createdAt; // Reference to a date-time object // The difference matters for: // 1. Null safety - primitives can't be null, references can // 2. Sharing - references can point to shared objects // 3. Identity - "==" means different things for primitives vs references}Reference attributes create potential for aliasing—where multiple references point to the same object. If you expose a reference (return it from a getter), external code can modify the object through that reference, potentially violating encapsulation. We address this with defensive copying, which we'll cover in the encapsulation chapter.
Design Implications:
When an attribute is a reference to another object, you're establishing a relationship:
These questions lead directly to concepts like aggregation and composition, which we'll explore in later modules.
Understanding the relationship between attributes and object identity is crucial for proper object-oriented design.
The Three Aspects of an Object:
Key Identifiers:
Some attributes serve a special role as identifiers—values that uniquely distinguish one object from another:
Student might be identified by studentIdBankAccount by accountNumberEmployee by employeeIdIdentifier attributes are typically:
1234567891011121314151617181920212223
public class Student { // The identifier - uniquely identifies this student private final String studentId; // Immutable, unique // Mutable state that can change without changing identity private String name; // Can change (marriage, legal name change) private String email; // Can change (new email provider) private double gpa; // Changes with each semester // Two students are "equal" if they have the same studentId @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student other = (Student) obj; return studentId.equals(other.studentId); // Identity-based equality } @Override public int hashCode() { return studentId.hashCode(); // Hash based on identifier }}When designing a class, one of your first decisions should be: what uniquely identifies an instance? This identifier attribute (or combination of attributes) will guide your implementation of equals() and hashCode(), and will likely be immutable. Getting this wrong causes subtle, hard-to-debug issues with collections and caching.
Well-documented attributes make code maintainable. Documentation should explain the purpose of an attribute, not just its type.
What to Document:
123456789101112131415161718192021222324252627282930313233
public class Reservation { /** * Unique identifier for this reservation. * Assigned at creation and never changes. * Format: "RES-" followed by 8 alphanumeric characters. * Used as the primary key in persistence. */ private final String reservationId; /** * The date and time when the reservation starts. * Always in UTC timezone. * Must be in the future at creation time. * Cannot be changed after confirmation. */ private LocalDateTime startTime; /** * The total price in cents (to avoid floating-point issues). * Always non-negative. * Updated when items are added/removed before confirmation. * Becomes immutable after confirmation. */ private int totalPriceCents; /** * Reference to the customer making this reservation. * Never null - every reservation must have a customer. * This is an association, not a composition - customer * lifecycle is independent of reservation lifecycle. */ private Customer customer;}If you find an attribute hard to document clearly, that's a design smell. It may indicate the attribute's purpose is unclear, it's doing too much, or it belongs in a different class. Clear documentation forces clear thinking.
We've explored attributes comprehensively. Let's consolidate the key insights:
What's Next:
Attributes are only half the story. While attributes capture what an object knows, methods define what an object can do. In the next page, we'll explore methods—the behaviors that operate on object state and provide the interface through which objects interact with the world.
You now understand attributes as the fundamental carriers of object state. You can distinguish instance from static, mutable from immutable, and primitive from reference attributes. Next, we'll explore methods—the behaviors that bring objects to life.