Types of data members
-
Last Updated: July 26, 2023
-
By: javahandson
-
Series
Learn Java in a easy way
Learn how data members in Java work: instance versus static fields, memory, default values, initializer blocks, access modifiers, and the mistakes beginners keep making.
Data members in Java are the variables you declare inside a class. They hold the state of your objects, and they come in exactly two flavours.
Picture a school. Every student has a roll number and a name, and those differ from student to student. But the school name is the same for all of them.
That is the whole idea in one sentence. Some values belong to each object, and some belong to the class as a whole.
Java calls the first kind instance data members. It calls the second kind static data members. Get this split right, and a surprising number of bugs simply never happen.
We will build this up slowly, with plenty of small examples. Here is the plan:
If you have written a simple class before, you already know enough to follow along.
A data member is a variable declared directly inside a class, outside every method. Java developers usually just call them fields.
public class Student {
int rollNumber; // data member (field)
String name; // data member (field)
void study() {
String subject = "Java"; // NOT a data member, it is local
}
}Notice subject sits inside a method. That makes it a local variable, and it disappears the moment study() finishes.
The two fields above behave very differently. They stick around for as long as the object does.
Java splits data members into exactly two groups:
The only visible difference is one keyword. Add static, and the behaviour flips completely.
Declare a field without static, and you get an instance member. Each object you create carries its own private copy.
public class Student {
int studentId;
String name;
public static void main(String[] args) {
Student student1 = new Student(); // gets its own memory
student1.studentId = 101;
student1.name = "Rohit";
Student student2 = new Student(); // gets separate memory
student2.studentId = 102;
student2.name = "Virat";
System.out.println(student1.studentId); // Output: 101
System.out.println(student2.studentId); // Output: 102
}
}Change student1 and student2 never notices. Two objects, two separate boxes, no interference.
Writing the class reserves nothing. A class is only a blueprint, and blueprints do not take up space.
Memory shows up the moment you call new. That is when the JVM carves out room on the heap for the fields.
Create ten Student objects, and you get ten separate studentId boxes. Create none, and you get zero.
Here is a genuine convenience. Instance members never sit empty, because Java fills them in for you.
int and long start at 0float and double start at 0.0boolean starts as falsechar starts as the null characternullpublic class Student {
int studentId; // becomes 0
String name; // becomes null
boolean active; // becomes false
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.studentId); // Output: 0
System.out.println(s.name); // Output: null
System.out.println(s.active); // Output: false
}
}Local variables get no such treatment. Read one before assigning it, and the compiler refuses to build your code.
This error catches almost every beginner. You cannot touch an instance field directly from main.
public class Student {
String name;
public static void main(String[] args) {
// name = "Virat";
// error: non-static variable name cannot be referenced
// from a static context
}
}Think about why. The main method is static, so it runs before any object exists.
Java has a fair question for you. Which student’s name did you mean, when no student has been created yet?
The fix is simple. Make an object first, then go through it.
public static void main(String[] args) {
Student student = new Student();
student.name = "Virat"; // works, we have an object now
System.out.println(student.name); // Output: Virat
}Put static in front of a field, and the rules change entirely. Now only one copy exists, no matter how many objects you build.
public class Student {
static String schoolName = "Java High"; // one copy, shared
String name; // one copy per object
}Every Student shares that single schoolName box. They each keep their own name.
A static field does not wait for new. The JVM sets aside its memory when the class first loads.
The classloader pulls the bytecode from disk into memory, and static fields come alive right then. No object required.
That timing explains a lot. It is exactly why you can reach a static field before creating a single object.
Since everyone shares one box, a change through one object shows up in all of them.
public class Student {
static int studentId; // shared by every object
String name;
public static void main(String[] args) {
Student student1 = new Student();
student1.studentId = 101;
student1.name = "Rohit";
Student student2 = new Student();
student2.name = "Virat"; // we never set studentId here
System.out.println(student1.studentId); // Output: 101
System.out.println(student2.studentId); // Output: 101
}
}Look at that last line closely. We never assigned studentId through student2, yet it prints 101.
This is the power of static, and also the danger. A roll number should never be shared, so that field has no business being static.
Reaching a static field through an object works, but it reads badly. It hints that the value belongs to that object, which is a lie.
Go through the class name instead. The intent becomes obvious.
Student.studentId = 101; // clear: this belongs to the class System.out.println(Student.studentId); // Output: 101
Most IDEs will even warn you when you access a static member through an instance. Listen to that warning.
Here is where static genuinely shines. Suppose you want to know how many Student objects have been created.
A shared counter solves it perfectly, because the count belongs to the class and not to any one student.
public class Student {
static int count = 0; // shared across all objects
String name;
public Student(String name) {
this.name = name;
count++; // every new object bumps the same counter
}
public static void main(String[] args) {
new Student("Rohit");
new Student("Virat");
new Student("Dhoni");
System.out.println(Student.count); // Output: 3
}
}Try that with an instance field and it breaks immediately. Each object would start its own counter at zero.
Keep this table handy. It answers most questions people have about data members in Java.
| Aspect | Instance member | Static member |
|---|---|---|
| Keyword | None | static |
| How many copies | One per object | One per class |
| Memory allocated | When you call new |
When the class loads |
| Lives in | The heap, with the object | The method area |
| Best access | Through an object | Through the class name |
Reachable from main |
Only via an object | Directly |
| Gets a default value | Yes | Yes |
| Good for | Data that differs per object | Data shared by everything |
If you memorise one row, pick the second. Everything else follows from it.
The JVM splits memory into regions, and your variables land in different ones. Knowing which is which explains most of their behaviour.
Now the lifetimes make sense. A stack frame disappears when the method returns, so local variables vanish with it.
An object survives on the heap until nothing references it. Then the garbage collector reclaims it, and its instance members go too.
Static members outlive everything. They stay in the method area for as long as the class stays loaded, which is usually the whole program.
Picture a company. Each employee has a desk with their own nameplate, and that is an instance member on the heap.
The company logo hangs once in reception. Everyone shares it, and it belongs to the company rather than to any employee. That is a static member.
A scribbled note on a whiteboard during one meeting is a local variable. When the meeting ends, somebody wipes the board.
Sometimes a plain value is not enough to set up a field. Maybe you need a loop, or a try-catch. Java gives you initializer blocks for exactly that.
A static block runs once, when the class loads. It runs before any object exists, and it never runs again.
public class Config {
static String environment;
static {
environment = System.getProperty("env", "production");
System.out.println("Static block ran, env = " + environment);
}
}Use it to prepare static fields that need real logic. Loading a driver or reading a config file are classic examples.
An instance block runs every single time you create an object. It fires just before the constructor body.
public class Student {
String name;
{ // instance initializer block
name = "Unknown";
System.out.println("Instance block ran");
}
public Student() {
System.out.println("Constructor ran");
}
}Honestly, you will rarely need this one. A constructor usually does the job more clearly.
Exam questions love this, and so do interviewers. Java follows a strict order:
new, instance fields and instance blocks runpublic class Demo {
static { System.out.println("1. static block"); }
{ System.out.println("2. instance block"); }
public Demo() { System.out.println("3. constructor"); }
public static void main(String[] args) {
new Demo();
new Demo();
}
}
Output:
1. static block
2. instance block
3. constructor
2. instance block
3. constructorRead that output carefully. The static block appears once, while the other two repeat for every object.
People lump these together, and that causes real confusion. Java treats them very differently.
| Aspect | Data member (field) | Local variable |
|---|---|---|
| Declared | In the class body | Inside a method or block |
| Default value | Yes (0, false, null) | None, you must assign it |
| Lives in | Heap or method area | The stack |
| Lifetime | As long as the object or class | Until the method returns |
| Access modifiers | Allowed | Not allowed |
One rule captures most of it. Fields always get a default value, and local variables never do.
Every data member can carry an access modifier. That modifier decides who is allowed to see it.
private keeps the field inside its own classprotected adds subclasses, even in other packagespublic throws it open to the entire programMake your fields private and expose them through getters and setters. This is encapsulation, and it pays off fast.
public class Student {
private int marks; // nobody outside can touch this directly
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException("Marks must be 0 to 100");
}
this.marks = marks; // validation happens before assignment
}
public int getMarks() {
return marks;
}
}Leave marks public and anyone can set it to -500. The setter gives you a place to say no.
Look closely at that setter. The parameter and the field share a name, which creates an ambiguity.
Java always picks the narrower scope, so the plain name marks refers to the parameter. Writing marks = marks would just assign the parameter to itself.
The keyword this breaks the tie. It means “the current object”, so this.marks reaches the data member.
public void setMarks(int marks) {
this.marks = marks; // field = parameter
// marks = marks; // useless, assigns the parameter to itself
}Forget the this and your field silently stays at 0. The code compiles happily, which makes the bug harder to spot.
Reach for static when the value genuinely belongs to the class rather than to any one object:
static final double PI = 3.14159Plenty of people add static for the wrong reasons. Watch out for these:
mainAsk yourself one honest question before typing static. Should every object in this program really share this value?
This is the classic. A student id turns static, and suddenly every student has the same id.
The compiler stays quiet, because nothing is technically wrong. Your data quietly turns to nonsense at runtime instead.
public class Student {
static int studentId; // bug: should be an instance member
}
Student a = new Student();
Student b = new Student();
a.studentId = 101;
System.out.println(b.studentId); // Output: 101, but b was never assignedRemove the keyword and the problem disappears. Each student gets a private id again, exactly as you intended.
You hit the “non-static variable cannot be referenced from a static context” error, so you slap static on the field.
Resist that urge. The error is telling you something true, namely that no object exists yet. Create one.
Every object shares a static field, which also means every thread shares it.
Picture two threads running count++ at the same time. Both read 5, both write 6, and one increment simply vanishes.
The operation looks atomic in your source code, but it is not. Under the hood it reads, adds, then writes, and a thread can be interrupted midway.
Keep static fields final where you can. When they must change, guard them with synchronisation or use AtomicInteger.
Public fields let any class reach in and change your object however it likes. Validation becomes impossible.
Worse, you can never add a rule later without breaking every caller. The moment a field goes public, it becomes part of your contract.
Default to private. Open things up later, only when you have a reason.
You can mark any data member final. That gives it exactly one assignment, and then it locks forever.
Pair the two keywords and you get a real constant. One shared copy, and nobody can ever change it.
public class Circle {
static final double PI = 3.14159; // one copy, locked forever
double radius;
double area() {
return PI * radius * radius;
}
}Notice the naming. Constants use UPPER_SNAKE_CASE, so any reader spots them instantly.
Almost every constant you meet in Java looks like this. Integer.MAX_VALUE and Math.PI both follow the same pattern.
Drop the static and the meaning shifts. Each object still gets its own copy, but each copy locks after one assignment.
public class Student {
private final int rollNumber; // one per object, set once
public Student(int rollNumber) {
this.rollNumber = rollNumber; // the only chance to assign it
}
}Every student gets a different roll number, yet no student can ever change theirs. That combination is genuinely useful.
Java lets you assign a final instance field in the constructor. After that, the door closes.
Let us pull the whole article into one small class. A bank account uses both kinds of data member.
The bank name and the interest rate are the same for every account. The balance and the holder are not.
public class Account {
static final String BANK = "Java Bank"; // constant, shared
static int accountCount = 0; // shared counter
private final String holder; // one per object, locked after construction
private double balance; // one per object, changes often
public Account(String holder, double opening) {
this.holder = holder;
this.balance = opening;
accountCount++; // every new account bumps the shared count
}
void deposit(double amount) {
double updated = balance + amount; // local variable
balance = updated;
}
public static void main(String[] args) {
Account a = new Account("Rohit", 1000);
Account b = new Account("Virat", 5000);
a.deposit(500);
System.out.println(a.balance); // Output: 1500.0
System.out.println(b.balance); // Output: 5000.0
System.out.println(Account.accountCount); // Output: 2
System.out.println(Account.BANK); // Output: Java Bank
}
}Walk through it once more. Every idea from this article turns up somewhere:
BANK is static and final, so one shared copy that nobody can reassignaccountCount is static, so both accounts increment the very same counterholder is final but not static, giving each account its own locked namebalance is a plain instance field, different per account and free to changeupdated is local, so it dies the moment deposit returnsLook at the two balances. They moved independently, exactly as instance members should.
Now look at the count. It reached 2, because static members refuse to be duplicated.
A: An instance data member gets a fresh copy in every object, and its memory arrives when you call new. A static data member has just one copy for the whole class, created when the class loads. Every object shares that single copy.
A: No. The main method is static, so it runs before any object exists. Java would not know which object’s field you meant. Create an object first, then reach the field through it.
A: The JVM allocates it once, when the classloader first loads the class into memory. It does not wait for an object. That is exactly why you can use a static field without ever calling new.
A: Both objects read and write the exact same memory, because only one copy exists. A change made through one object becomes visible through every other object immediately, and through the class name too.
A: Yes. Both instance and static members start at 0, 0.0, false, or null depending on their type. Local variables are the exception. They get nothing, so you must assign a value before reading one.
A: Static blocks run first, once, when the class loads. After that, every call to new runs the instance block, then the constructor body. So a static block appears once in the output, while the other two repeat per object.
A: Yes, and that is the preferred way. Use the class name, such as Student.count. Reaching a static field through an object still compiles, but it wrongly suggests the value belongs to that object.
A: Instance variables live on the heap, inside the object that owns them. Static variables live in the method area, which belongs to the class itself. Local variables live on the stack and vanish when the method returns.
A: Because the count belongs to the class, not to any single object. A static counter survives across every new call and keeps one running total. An instance counter would reset to zero inside each new object.
A: A static final member is a true constant. One copy exists for the class, and nobody can reassign it. A plain final member gives each object its own copy, which locks after a single assignment, usually inside the constructor. So every object can hold a different value, yet none of them can change it later.
A: The parameter shares its name with the data member, so the plain name refers to the parameter. Adding this points Java at the current object’s field instead. Leave out the this and you assign the parameter to itself, which compiles fine but leaves the field untouched.
A: Not by default. One copy is shared by every object, which also means every thread touches the same memory. Two threads updating the same static counter can lose an update. Keep static fields final, or guard them with synchronisation.
A: Yes, and Java calls that shadowing. Inside the method, the local name wins, and the data member hides behind it. Reach the field with this.name whenever you need it.
Let us wrap up what we covered. Data members are the fields inside a class, and Java offers two kinds.
Instance members give each object its own copy, allocated when you call new. Use them for data that genuinely differs between objects, like a name or a roll number.
Static members hand one shared copy to the entire class, allocated the moment the class loads. Save them for constants, counters, and configuration that everything should share.
Before you type static, pause and ask the question one more time. Should every object really share this value? If the answer is no, leave the keyword out.
Keep your fields private, reach them through getters and setters, and let the compiler protect you. Those habits pay off long before your first big project.