Variables and Constants in Java
-
Last Updated: July 4, 2023
-
By: javahandson
-
Series
Learn Java in a easy way
Learn how variables and constants in Java work: declaration, initialization, the three variable types, scope, the final keyword, and the naming rules every beginner should know.
Variables and constants in Java are the very first things you will write in any program. A variable holds a value that can change. A constant holds a value that must stay put.
Think about a scoreboard at a cricket match. The score changes ball by ball, so that is a variable. The number of players in a team stays at eleven, so that is a constant.
Your Java code works the same way. Some values move around while the program runs. Others are fixed the moment you set them, and any attempt to change them should fail loudly.
Getting this distinction right early saves you real pain later. Many bugs come from a value changing when nobody expected it to change.
We will start from the basics and build up slowly. Here is the plan:
You do not need any prior knowledge beyond basic data types. If you know what an int is, you are ready.
A variable is a named box in memory. You put a value inside, and you use the name to find it again.
The computer does not really care about names. It works with raw memory addresses, something ugly like 0x4560XT. Nobody wants to type that.
So Java lets you hand that address a friendly label. You write score, and the compiler quietly maps it to the right spot in memory.
Java calls that label an identifier. Pick a good one, because you will read it far more often than you write it.
The word “variable” comes from “vary”. The value inside the box can vary while the program runs.
Say you store 50 in a box named result. Later you add 50 more. Now the same box holds 100, and the name never changed.
int result = 50; result = result + 50; System.out.println(result); // Output: 100
Notice what happened on line two. Java read the old value, added 50, then wrote the answer back into the same box.
Every variable needs a data type. The type tells Java how big the box should be.
byte reserves 1 byte of memoryint reserves 4 byteslong reserves 8 bytesdouble reserves 8 bytesboolean needs just a single bit of informationPick a type that fits the data. Storing someone’s age in a long works, but it wastes seven bytes for no reason.
Pick a type that is too small, though, and the compiler stops you. A byte cannot hold 200, and Java will not let you pretend otherwise.
These two words get mixed up constantly. They are not the same thing, and the difference matters.
Declaration is when you tell Java the type and the name. That reserves the memory, but puts nothing in it yet.
int result; // declared, but empty String name; // declared, but empty
The syntax is simple. Write the data type, then a space, then your chosen name.
You must declare a variable before you use it. Skip this step and the compiler throws an error straight away.
Initialization is when you actually put a value inside. It uses the equals sign.
int result; result = 50; // now it holds something
Until that second line runs, the box sits empty. Reading it early is a mistake we will come back to in section 10.
Most of the time you declare and initialize together. It is shorter, and it removes any chance of reading an empty box.
int result = 50; String name = "Rohit"; double price = 19.99; boolean active = true;
Prefer this style whenever you can. Fewer lines, and fewer ways to get it wrong.
Once a variable holds a value, you can overwrite it as often as you like.
int score = 10; score = 20; // replaced score = score + 5; // read, add, write back System.out.println(score); // Output: 25
Each assignment throws away the old value. There is no history, and no undo.
Java sorts variables by where you declare them. That position changes their lifetime, their memory, and their default value.
A local variable lives inside a method, a constructor, or a block. It is born when the method starts, and it dies when the method ends.
Here is the rule that trips up every beginner. Local variables get no default value. You must assign one yourself before reading it.
void calculate() {
int total; // no default value
total = 10 + 20; // assign before use
System.out.println(total); // Output: 30
}Try to print total before assigning it, and the code will not compile. Java would rather stop you now than hand you garbage later.
An instance variable sits inside the class but outside any method. Every object gets its own private copy.
public class Student {
int rollNumber; // instance variable, defaults to 0
String name; // instance variable, defaults to null
}Create two Student objects and you get two separate rollNumber boxes. Change one, and the other never notices.
Unlike local variables, these do get default values:
boolean defaults to falseAdd the static keyword and everything changes. Now the class keeps only one copy, and every object shares it.
public class Student {
static String schoolName = "Java High"; // shared by all
int rollNumber; // one per object
}The JVM sets aside memory for a static variable once, right when the class loads. It does not wait for an object.
Change schoolName through one student, and every student sees the new value. That is the whole point, and also the whole danger.
| Aspect | Local | Instance | Static |
|---|---|---|---|
| Declared in | A method or block | The class body | The class body, with static |
| Default value | None | Yes (0, false, null) | Yes (0, false, null) |
| How many copies | One per method call | One per object | One per class |
| Lives until | The method ends | The object is collected | The class unloads |
If you remember one row, make it the second. Local variables never get a freebie.
Scope means the region of code where a name is visible. Step outside that region, and the name simply does not exist.
A variable declared inside a loop dies at the closing brace. Ask for it afterwards, and the compiler shrugs.
for (int i = 0; i < 3; i++) {
int doubled = i * 2; // scope: inside the loop only
}
// System.out.println(doubled); // error: cannot find symbolNarrow scope is a good thing. Fewer places to touch a variable means fewer places for a bug to hide.
Java always looks at the narrowest scope first. So a local variable can hide an instance variable that shares its name.
That hiding has a name: shadowing. It is legal, and it is confusing.
public class Shadow {
int value = 10; // instance variable
void show(int value) { // parameter shadows it
System.out.println(value); // Output: 50
System.out.println(this.value); // Output: 10
}
public static void main(String[] args) {
new Shadow().show(50);
}
}The keyword this means “the current object”. Put it in front of a name, and Java skips the local scope entirely.
You will see this constantly in setter methods, where the parameter deliberately matches the field name.
public void setName(String name) {
this.name = name; // field = parameter
}Drop the this there and the line becomes name = name. It compiles, does nothing useful, and the field stays null.
A constant is a value that must not change once set. Java gives you one keyword for the job: final.
You can apply it in three places, and it means something slightly different in each.
Beginners often ask a fair question. Why not just use a normal variable and promise not to change it?
Because promises break. Constants give you three concrete wins:
MAX_SPEED knows the value is fixed, without hunting through the fileThat last point matters most in real projects. Scatter the number 0.18 across twenty methods, and one day you will update nineteen of them.
Give it a name once, and the problem disappears for good.
Mark a variable final, and you get exactly one chance to assign it.
final int RESULT = 50; // RESULT = RESULT + 10; // error: cannot assign a value to final variable
You can also declare it first and assign it later. Java simply insists that it happens once.
final int limit; limit = 100; // fine, first assignment // limit = 200; // error, second assignment
Subclasses can call a final method, but they can never override it. The logic stays exactly as you wrote it.
Use this when the behaviour must never drift. Adding two numbers, for instance, has exactly one correct answer.
public class Addition {
final int add(int a, int b) {
return a + b;
}
}
class Operation extends Addition {
int add(int a, int b) { // error
return a * b;
}
}
// Output: add(int,int) in Operation cannot override add(int,int) in Addition
// overridden method is finalNobody can extend a final class. Inheritance stops right there.
final public class Addition {
int add(int a, int b) { return a + b; }
}
class Operation extends Addition { }
// Output: cannot inherit from final AdditionJava uses this trick itself. The String class is final, which is part of why strings behave so predictably.
One keyword, three jobs. Notice the theme: final always locks something down.
Here is the single biggest misunderstanding about constants in Java. Read this section twice.
When a final variable holds an object, final freezes only the reference. The object it points at can still change freely.
final List<String> names = new ArrayList<>();
names.add("Rohit"); // allowed, the list contents changed
names.add("Virat"); // still allowed
System.out.println(names); // Output: [Rohit, Virat]
// names = new ArrayList<>(); // error: cannot assign a value to final variableThink of it like a house address written in permanent ink. You cannot change the address, but you can still rearrange the furniture inside.
If you want the contents locked too, final is not enough. You need an immutable object.
List.of(...) or Map.of(...) to build collections that reject changesString and Integerfinal List<String> names = List.of("Rohit", "Virat");
names.add("Dhoni"); // throws UnsupportedOperationException at runtimeNow nothing moves. The reference stays put, and the contents refuse every change. That is genuine immutability.
Not every final variable is equal. Some are special, and the compiler treats them differently.
A compile-time constant must tick three boxes:
finalfinal int MAX = 10; // compile-time constant final int limit = getLimit(); // NOT one, value known only at runtime
Here is the simple test. Could you replace the name with its value by hand, just by reading the code? If yes, it is a compile-time constant.
Only compile-time constants may appear as case labels. The compiler builds its jump table before your program runs, so it needs the values up front.
final int MAX = 10;
final int limit = getLimit();
switch (x) {
case MAX: // fine
break;
case limit: // error: constant expression required
break;
}Hit that error one day and you will know exactly why. The value was not knowable at compile time.
The compiler enforces some naming rules. Others are just conventions, but breaking them will annoy your teammates.
Break any of these and your code will not compile:
class and int will not work_ and $score and Score are two different variablesint studentResult; // valid int _count; // valid, though unusual // int 2ndPlace; // error, starts with a digit // int class; // error, class is a keyword // int total marks; // error, contains a space
These are not compiler rules. They are habits, and every Java team expects them.
result, not ResultstudentResultMAX_SPEED, STUDENT_RESULTtotalMarks beats tm every timeiint studentResult = 150; // camelCase variable static final int MAX_SPEED = 120; // UPPER_SNAKE_CASE constant
Follow the case convention and anyone can tell a constant from a variable at a glance. That is the whole point.
Java 10 added a small but handy keyword: var. It lets you skip writing the type when the compiler can already work it out.
Look at the right-hand side of the assignment. If the type is obvious there, var saves you from repeating it.
var count = 10; // compiler infers int var name = "Rohit"; // compiler infers String var prices = new ArrayList<Double>(); // infers ArrayList<Double>
One thing catches people out. var is not a dynamic type, and Java is still strongly typed.
The type gets fixed at compile time and never changes afterwards. Try to reassign a different type and the compiler stops you.
var count = 10; // count = "ten"; // error: incompatible types: String cannot be converted to int
The keyword only works for local variables. Java needs an initializer to infer from, so several spots are off limits:
varvarvar x;, will not compilenull gives the compiler nothing to work withUse it where it genuinely helps readability. Writing var in front of a long generic type is a win, but var x = 1; just hides useful information.
Let us put the whole article together in one small program. We will price an order, using both variables and a constant.
The tax rate never changes during the run, so it earns a constant. Everything else moves, so those become plain variables.
public class Bill {
static final double TAX_RATE = 0.18; // constant, shared by the class
private String customer; // instance variable, one per object
public Bill(String customer) {
this.customer = customer; // this breaks the shadowing tie
}
double total(double price, int quantity) {
double subtotal = price * quantity; // local variable
double tax = subtotal * TAX_RATE; // local variable
return subtotal + tax;
}
public static void main(String[] args) {
Bill bill = new Bill("Rohit");
System.out.println(bill.total(100.0, 3)); // Output: 354.0
}
}Walk through it once more, and notice how every idea from this article shows up:
TAX_RATE is static and final, so one shared copy that nobody can reassigncustomer is an instance variable, so every Bill object carries its ownsubtotal and tax are local, so they vanish when the method returnsthis.customer because the parameter shadows the fieldTry changing TAX_RATE inside the total method. The compiler will refuse, and that refusal is exactly the protection you wanted.
Local variables have no default value. Read one before assigning it, and the compiler stops you cold.
int total; System.out.println(total); // error: variable total might not have been initialized
The fix is easy. Assign a value, even a zero, before the first read.
We covered this in section 7, and it deserves a second mention. A final List can still gain and lose elements.
If the contents must not change, reach for List.of(...). Do not rely on final alone.
Naming a local variable the same as a field is legal. It is also a fine way to confuse yourself at 2am.
Either use this.field deliberately, or pick a different local name. Do not leave it ambiguous.
Marking a field static by accident is a classic bug. Suddenly every object shares one value.
public class Student {
static int rollNumber; // oops, should not be static
}
Student a = new Student();
Student b = new Student();
a.rollNumber = 101;
System.out.println(b.rollNumber); // Output: 101, not 0Ask yourself one question before adding static. Should every object really share this value?
A: You can reassign a variable as many times as you like while the program runs. A constant uses the final keyword, so you get exactly one assignment. Any later attempt to change it gives you a compile-time error.
A: No. final only locks the reference, not the object it points to. A final List can still have elements added or removed. For true immutability, use something like List.of(…) or design your class with private final fields and no setters.
A: Local variables have no default value at all. Instance and static variables get defaults like 0, false, or null. With a local variable, you must assign a value yourself before reading it, or the code will not compile.
A: Shadowing happens when a local variable or method parameter has the same name as an instance variable. Inside that method, the local name wins. To reach the instance variable, you have to qualify it with this, as in this.value.
A: It is a final variable of a primitive or String type, initialized with a literal value the compiler can evaluate immediately. Only compile-time constants can be used as case labels in a switch statement, because the compiler needs those values before the program runs.
A: Yes. You can declare a final variable and assign it later, as long as you assign it exactly once. Java calls this a blank final. A second assignment gives you a compile-time error.
A: Constants use UPPER_SNAKE_CASE, so all capital letters with underscores between words. MAX_SPEED and STUDENT_RESULT are good examples. The compiler does not enforce this, but every Java team expects it.
A: Exactly one, no matter how many objects you create. The JVM allocates that memory once when the class loads, and every object shares it. Change it through one object and all the others see the new value.
A: No. The var keyword only works for local variables that have an initializer. Instance variables, static variables, and method parameters must all declare an explicit type. Java needs something on the right-hand side to infer from.
A: The parameter shadows the instance variable, because both share the same name. Java resolves the plain name to the parameter, so this.name tells it to use the field instead. Drop the this and you simply assign the parameter to itself, leaving the field null.
A: Local variables live on the stack, inside the frame for that method call. When the method returns, the frame goes away and the variables vanish with it. Instance variables live on the heap instead, alongside the object that owns them.
Let us wrap up what we covered. A variable is a named box in memory whose value can change. A constant uses final, takes exactly one assignment, and then locks.
Where you declare a variable decides how it behaves. Local variables get no default and die with the method. Instance variables give every object its own copy. Static variables hand one shared copy to the whole class.
The final keyword works at three levels. It stops you reassigning a variable, stops a subclass overriding a method, and stops anyone extending a class.
Just remember the trap. Final locks the reference, not the object behind it. If the contents must stay fixed, reach for a genuinely immutable type.