A class in Java describes a kind of thing: the shape of a product, the data it carries, and the things it can do. An object is one actual instance of that description, sitting in memory while your program runs. Splitting those two ideas apart is the first step towards code that stays readable once it grows past a screenful.
What is the difference between a class and an object?
The class is the blueprint. It lives in a file, it is loaded once, and it holds no per-item data. The object is the thing you build from that blueprint. Every object gets its own copy of the fields, and each one can be changed without touching the others. If you have written code in a language where a "record" was both the description and the data, the mental shift is worth making explicitly: the description and the data are separate things in Java, and the new keyword is the act of turning one into the other.
Here is a class with three fields and two methods, then two objects built from it:
public class Demo {
static class Product {
String name;
double price;
int stock;
void restock(int units) {
stock += units;
}
boolean isInStock() {
return stock > 0;
}
}
public static void main(String[] args) {
Product keyboard = new Product();
keyboard.name = "Mechanical keyboard";
keyboard.price = 89.99;
keyboard.stock = 4;
Product mouse = new Product();
mouse.name = "Wireless mouse";
mouse.price = 24.50;
mouse.stock = 0;
System.out.println(keyboard.name + " costs " + keyboard.price);
System.out.println("keyboard in stock: " + keyboard.isInStock());
System.out.println("mouse in stock: " + mouse.isInStock());
mouse.restock(10);
System.out.println("mouse after restock: " + mouse.isInStock() + " (" + mouse.stock + " units)");
}
}Running that program prints the keyboard's name and price, the two stock answers, and then the mouse after a restock of ten units. Notice the shape of the calls: isInStock() is a question the object answers about itself, so the calling code never has to reach inside and compare stock itself. When every part of your program asks the object rather than poking at its fields, the object can change how it stores things later without breaking the callers.
What actually goes inside a class definition?
Three things, in this order: fields, constructors and methods. Fields hold the state. Constructors decide how a new object gets its initial state. Methods are the verbs — the things the object can do. The order you write them in does not matter to the compiler, but people read a class top-down, so state first and behaviour second is a habit worth keeping.
Each field has a name, a type, and optionally an initial value. Leaving the value out is not an error, and that is where beginners get surprised: a field without an initializer starts at the zero value for its type. Numbers start at 0, booleans at false, and objects start with no value at all, which is why the very first line of a fresh object often needs a null check before you touch it.
| Piece | Holds | Example |
|---|---|---|
| Field | One piece of state per object | double price; |
| Constructor | How a new object is set up | Product(String name) |
| Method | Behaviour, plus optionally a result | boolean isInStock() |
Fields can also be marked final, which means the reference cannot be pointed somewhere else after it is set. That is a good default for things that identify an object, such as a registration number or an id.
Why does changing an object through a second variable change both?
Because a variable that holds an object holds a reference to it, not a copy of it. Java has no built-in "copy this object" operation hiding behind the assignment operator, and that is deliberate: if a copy happened silently, then editing a copy would leave you wondering which of the two you are actually looking at.
public class Demo {
static class Product {
String name;
double price;
}
public static void main(String[] args) {
Product a = new Product();
a.name = "Desk lamp";
a.price = 39.0;
Product b = a;
b.name = "Floor lamp";
Product c = new Product();
c.name = "Desk lamp";
c.price = 39.0;
System.out.println(a.name + " / " + b.name);
System.out.println("a == b: " + (a == b));
System.out.println("a == c: " + (a == c));
System.out.println("a.equals(c): " + a.equals(c));
}
}The output shows Floor lamp / Floor lamp on the first line, because b and a are two names for one object. The second product, c, holds the same values but is a different object, so it never sees the rename. This is the single most common surprise in Java for people arriving from a language where assignment copies data: mutating a "copy" changes the original.
If you genuinely want a second object with the same contents, you have to ask for one explicitly, usually by constructing it again.
When should you compare objects with == and when with equals?
Use == when you mean "these two variables point at the very same object". Use equals when you mean "these two objects carry the same data". In the example above, a == c prints false even though the two products hold identical names and prices, because == asks about identity.
Out of the box, equals on a plain class also means identity, so a.equals(c) prints false too. Classes that care about value have to say so by overriding equals, and any class that overrides equals should override hashCode as well so the object behaves correctly inside hash-based collections. A good rule while you are learning: treat two objects with the same contents as equal only when the class explicitly promises it.
How do you pass objects between methods?
You pass the reference, and the method receives a second name for the same object. That is why a method can change a caller's object without returning anything, and why it can also accidentally change it when the caller did not expect any change at all.
public class Demo {
static class Product {
String name;
double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
Product discounted(double percent) {
return new Product(name, price * (1 - percent / 100));
}
}
public static void main(String[] args) {
Product original = new Product("Standing desk", 349.00);
Product sale = original.discounted(15);
System.out.printf("original: %s %.2f%n", original.name, original.price);
System.out.printf("sale: %s %.2f%n", sale.name, sale.price);
}
}The discounted method returns a brand new product instead of editing the receiver, so the original price stays at 349.00 while the sale copy prints 296.65. Returning a new object is a habit worth picking up early: it makes the method safe to call from anywhere, and it plays nicely with the shared-state trap you just saw.
Why does a printed object look like gibberish?
Every object inherits a toString method from the base class, and the default version prints the class name plus a hash code that changes between runs. It is a debugging aid, not a description of your data, so the output is intentionally useless to a reader.
public class Demo {
static class Plain {
String name;
Plain(String name) {
this.name = name;
}
}
static class WithToString {
String name;
WithToString(String name) {
this.name = name;
}
@Override
public String toString() {
return "WithToString(" + name + ")";
}
}
public static void main(String[] args) {
Plain plain = new Plain("lamp");
WithToString custom = new WithToString("lamp");
System.out.println("plain type: " + plain.getClass().getSimpleName());
System.out.println("custom: " + custom);
}
}Overriding toString gives you readable output in logs and error messages. The first line deliberately prints only the class name via getClass().getSimpleName(), because printing the default form would put a hash code in your output that changes every time the program starts and would make any example you copy unrunnable-looking.
The class and object picture in one page
- A class is the description; an object is one live instance built from it with
new. - Fields hold per-object state, methods hold behaviour, constructors set up new objects.
- Variables that hold objects hold references, so two names can point at one object.
==compares identity,equalscompares values only when a class says so.- Passing an object to a method hands over a reference, not a copy.
- Override
toStringso printed objects say something useful.
Try it yourself: build a small plant-care tracker. Write a Plant class with a name, a waterEveryDays field, and a method needsWater(int daysSinceWatering) that returns true when the plant is due. Add a toString that prints the name and its watering interval. Then write a main method that creates three plants named Fern, Cactus and Basil, gives the Basil a seven-day interval and the other two a three-day one, then prints each plant with a reminder line built from needsWater using day counts of 2, 4 and 9. Run it, then deliberately point a second variable at the Fern and change its name to check that you really do share one object rather than two copies.