In Java, a class is a blueprint and an object is a thing built from that blueprint. The class declares what data each object holds and what it can do. It doesn't hold any data of its own until you create an object from it with new. Understanding the Java class and object relationship covers most of what beginners get wrong: why == returns false for two identical-looking objects, why a method can change your object but not replace it, and why printing an object shows Book@1b6d3586.
This guide covers class members, constructor rules, what new actually does, references, equals and toString, pass by value, overloading, and the difference between abstract classes and interfaces. All code targets Java 17+.
Java class vs object
| Class | Object (instance) | |
|---|---|---|
| What it is | A type definition, written in source code | A value in memory, created at runtime |
| How many | One per type | As many as you create |
| Created by | Writing class Book { ... } | new Book(...) |
| Holds data? | Only static fields | Its own copy of every instance field |
Two objects of the same class can hold completely different values. Execution starts at a public static void main(String[] args) method, and your code creates objects from there.
Class members: fields and methods
A class has two kinds of members:
- Fields (also called attributes or properties) store an object's data.
- Methods define its behavior, meaning what the object can do. Other objects interact with it by calling its methods.
public class Book {
public static final int MAX_STOCK = 999; // constant shared by the whole class
private final String isbn; // set once, never changes
private String title; // can be updated
private int stock;
public Book(String isbn, String title) {
this(isbn, title, 0); // delegate to the other constructor
}
public Book(String isbn, String title, int stock) {
this.isbn = isbn;
this.title = title;
this.stock = stock;
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public int getStock() { return stock; }
}A field can be a variable, which you can reassign, or a constant, marked final, which you can assign only once. A static field belongs to the class itself, so every Book shares the same MAX_STOCK. A non-static field such as title exists separately in every object.
Java constructors: the rules
A constructor is the code that runs when an object is created. Its job is to put the new object into a valid state.
- It has exactly the same name as the class.
- It has no return type, not even
void. If you writepublic void Book(), you've declared an ordinary method that happens to be calledBook, and it won't run when an object is created. - A class can have several constructors, as long as their parameter lists differ.
- If you write no constructor at all, the compiler adds a public no-argument default constructor. As soon as you write any constructor, that default disappears.
- One constructor can call another with
this(...), which must be the first statement. This avoids duplicating setup logic, as inBook(isbn, title)above.
What the new keyword does
new Book("978-0134685991", "Effective Java") goes through these steps:
- Allocates memory on the heap for the object's fields.
- Sets every field to its default value:
0for numbers,falseforboolean,nullfor references. - Runs the superclass constructor (every class extends at least
Object). - Runs field initializers such as
private String label = "ready";, in the order they appear. - Runs the rest of the constructor body.
- Returns a reference to the new object.
You can see the order in a small example:
public class Init {
private int count; // step 2: starts as 0
private String label = "ready"; // step 4: initializer
public Init() {
System.out.println(count + " " + label); // prints "0 ready"
count = 10; // step 5: constructor body
}
}References: variables hold addresses, not objects
A variable of a class type doesn't contain the object. It contains a reference to where the object lives. This explains what = does with objects:
Book a = new Book("978-0134685991", "Effective Java");
Book c = a; // copies the reference, not the object
c.setTitle("EJ 3rd ed.");
System.out.println(a.getTitle()); // "EJ 3rd ed." - a and c are the same objectc = a doesn't copy the book. You now have two names for one object. If you need an independent copy, create a new object yourself, for example with a copy constructor public Book(Book other) that copies each field.
Comparing objects: == vs equals()
==compares references, meaning "is this the same object?"equals()compares values, meaning "do these objects represent the same thing?", but only if the class overrides it.
The default equals() inherited from Object behaves exactly like ==. To compare books by ISBN, override it, and always override hashCode() together with it. Otherwise HashMap and HashSet will treat equal books as different keys.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book other)) return false; // pattern matching, Java 16+
return isbn.equals(other.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn);
}Book a = new Book("978-0134685991", "Effective Java");
Book b = new Book("978-0134685991", "Effective Java");
System.out.println(a == b); // false: two separate objects
System.out.println(a.equals(b)); // true: same ISBNThe same rule applies to strings: "java" == new String("java") is false, while .equals() is true. Compare strings with equals().
Readable output with toString()
Printing an object calls its toString(). The default from Object gives the class name, @, and the hash code in hex, for example Book@1b6d3586. That hex value is a hash code, not the memory address, and it doesn't help you debug. Override it:
@Override
public String toString() {
return "Book{isbn='" + isbn + "', title='" + title + "', stock=" + stock + "}";
}If a class only carries data, a record gives you the constructor, accessors, equals, hashCode and toString for free:
public record Point(int x, int y) {}
// new Point(1, 2).toString() -> "Point[x=1, y=2]"Java is always pass by value
Java passes every argument by value. For a primitive, the value is the number itself. For an object, the value is the reference, so the method gets a copy of the reference that points to the same object.
static void rename(Book book) { book.setTitle("Renamed"); } // changes the shared object
static void replace(Book book) { book = new Book("000", "New"); } // only changes the local copy
static void addOne(int n) { n++; } // only changes the local copy
Book a = new Book("978-0134685991", "Effective Java");
rename(a); // a.getTitle() is now "Renamed"
replace(a); // a still points to the original book, title still "Renamed"
int x = 5;
addOne(x); // x is still 5A method can modify the object you pass, but it can't make your variable point at a different object. "Objects are passed by reference" is a common but inaccurate summary, as replace() shows.
Method overloading
Overloading means declaring several methods with the same name but different parameter lists in the same class. The compiler picks one at compile time based on the argument types.
public void restock(int amount) {
restock(amount, "regular delivery");
}
public void restock(int amount, String reason) {
if (stock + amount > MAX_STOCK) {
throw new IllegalArgumentException("stock limit is " + MAX_STOCK);
}
stock += amount;
}The parameter lists must differ in number, type or order. A different return type alone isn't enough, and it won't compile. System.out.println is a familiar example, with overloads for int, String, Object and more. Overloading isn't the same as overriding, which redefines an inherited method in a subclass. That belongs to polymorphism, covered in the four pillars of Java OOP.
Abstract classes and interfaces
Some types exist only to be completed by other classes.
An abstract class is marked abstract and can't be instantiated with new. It can have fields, constructors and finished methods, plus abstract methods with no body that every concrete subclass must implement:
public abstract class Media {
private final String title;
protected Media(String title) { this.title = title; } // runs via super(title)
public String getTitle() { return title; }
public abstract int loanDays(); // no body: subclasses must provide one
}An interface isn't a class. It declares what a type can do. Its methods are abstract by default, and since Java 8 it can also have default and static methods with bodies. It can't have instance fields or constructors.
public interface Borrowable {
boolean isAvailable();
default String status() {
return isAvailable() ? "available" : "on loan";
}
}
public class Dvd extends Media implements Borrowable {
private boolean onLoan;
public Dvd(String title) { super(title); }
@Override public int loanDays() { return 3; }
@Override public boolean isAvailable() { return !onLoan; }
}If Dvd forgets to implement loanDays() or isAvailable(), the code doesn't compile. That compile-time check is the point of both constructs.
| Abstract class | Interface | |
|---|---|---|
Instantiate with new | No | No |
| Constructors | Yes | No |
| Instance fields | Yes | No (only static final constants) |
| Methods with a body | Yes | default, static, private methods |
| How many per class | extends one | implements many |
The syntax is the easy part. Deciding when to use each is a design question about abstraction, covered in the Java OOP pillars article. If you also write Go, compare this with Go's implicit interfaces, where a type never declares which interfaces it implements.
FAQ
What is the difference between a class and an object in Java?
A class is the definition: which fields and methods a type has. An object is an instance of that class, created at runtime with new, with its own values for the instance fields. One class can produce any number of objects.
Can a Java constructor return a value?
No. A constructor has no return type at all. If you add one, even void, it becomes a regular method and won't run when the object is created.
Is Java pass by value or pass by reference?
Always pass by value. For objects, the value copied is the reference, so a method can modify the object but can't reassign the caller's variable.
Why do I need to override hashCode when I override equals?
Hash-based collections look up the hash code first and call equals only within the matching bucket. If two equal objects return different hash codes, HashSet and HashMap will treat them as different entries.
Can an abstract class have a constructor?
Yes. You can't call it with new, but subclasses call it through super(...) to initialize the fields the abstract class declares.
Checklist for writing a Java class
- Make fields
private, and make themfinalwhen they shouldn't change after construction. - Validate input in the constructor so an object can't exist in an invalid state.
- Use
this(...)to chain constructors instead of copying setup code. - Compare values with
equals(), and overrideequalsandhashCodetogether. - Override
toString()so logs and debuggers show useful information, or use a record for plain data. - Remember that
=copies references, and that methods receive copies of references.
These basics carry over to every Java framework you'll use later. If your team needs help building or reviewing Java backend systems, Vectorkub works on that kind of project.
