In Java, everything begins with classes and objects. These two concepts are at the heart of object-oriented programming (OOP). In this section, you’ll learn how to define your own classes and create objects from them—laying the foundation for building modular, reusable, and maintainable software.
A class is a blueprint for creating objects. It defines the structure (fields or attributes) and behavior (methods or functions) that its instances will have. Think of a class as a template—like architectural drawings for a house.
public class Car {
// Fields (attributes)
String model;
int year;
// Method (behavior)
void startEngine() {
System.out.println("Engine started.");
}
}
In this example:
Car
is a class with two fields: model
and year
.startEngine()
.This class doesn’t actually do anything until we create objects from it.
An object is a specific instance of a class. When you create an object, you’re telling Java: “Make me one of these, with memory allocated for its fields.”
Each object has its own copies of the class’s fields, but it shares the behavior (methods) defined in the class.
To create an object in Java, you use the new
keyword followed by a call to the class's constructor (a special method we'll cover in the next section).
Car myCar = new Car(); // Object 1
Car yourCar = new Car(); // Object 2
new Car()
creates a new object in memory.myCar
and yourCar
are references to the respective objects.You can then assign values to the fields and call methods:
myCar.model = "Toyota Corolla";
myCar.year = 2020;
yourCar.model = "Honda Civic";
yourCar.year = 2022;
myCar.startEngine(); // Output: Engine started.
yourCar.startEngine(); // Output: Engine started.
Each object maintains its own state (model
and year
), even though they share the same structure and behavior.
When you write:
Car myCar = new Car();
here’s what happens under the hood:
new Car()
allocates memory in the heap for the new object.myCar
.It’s important to understand that myCar
is not the object itself—it’s a reference (like a pointer) to the object in memory. You can have multiple references to the same object:
Car anotherCar = myCar;
anotherCar.model = "Tesla Model 3";
System.out.println(myCar.model); // Output: Tesla Model 3
Since both myCar
and anotherCar
point to the same object in memory, changes made through one reference affect the other.
Let’s tie it all together with a complete, simple program:
public class Car {
String model;
int year;
void displayInfo() {
System.out.println("Model: " + model + ", Year: " + year);
}
}
public class Main {
public static void main(String[] args) {
Car car1 = new Car();
car1.model = "Ford Mustang";
car1.year = 2023;
Car car2 = new Car();
car2.model = "Chevrolet Camaro";
car2.year = 2021;
car1.displayInfo(); // Output: Model: Ford Mustang, Year: 2023
car2.displayInfo(); // Output: Model: Chevrolet Camaro, Year: 2021
}
}
new
keyword allocates memory and returns a reference to the object.Understanding the difference between class as blueprint and object as instance is critical for writing effective Java programs. In the next section, you’ll dive deeper into fields and methods—the components that give objects their unique identity and purpose.
In object-oriented programming, fields and methods are the core building blocks of a class. Fields define the data or state that an object holds, while methods define the behaviors or actions that the object can perform. Together, they form the structure and functionality of Java classes.
Understanding how to declare and use fields and methods is essential for writing well-structured and reusable code. This section covers the fundamentals of fields and methods in Java, including access control, method parameters, and variable scope.
Fields, also known as instance variables, are declared inside a class but outside any method. Each object created from a class gets its own copy of the class’s fields. Fields hold the data that represents the state of the object.
public class Person {
// Fields (instance variables)
public String name;
private int age;
}
In this example:
name
is a public
field, accessible from outside the class.age
is private
, meaning it can only be accessed within the Person
class.It’s a best practice to make fields private and access them via public methods (getters and setters) to preserve encapsulation.
Methods define what an object can do. A method is a block of code that performs a specific task, and it can operate on the fields of the class.
public class Person {
private String name;
// Method with no return value
public void greet() {
System.out.println("Hello, my name is " + name);
}
// Method with parameters and a return type
public void setName(String newName) {
name = newName;
}
public String getName() {
return name;
}
}
Here:
greet()
is a method that prints a message using the name
field.setName()
takes a parameter and sets the internal name
field.getName()
returns the current value of the name
field.Java uses access modifiers to control the visibility of class members (fields and methods):
public
: Accessible from any other class.private
: Accessible only within the same class.protected
: Accessible within the same package or subclasses.Encapsulation encourages using private
fields and providing controlled access via public
methods.
It’s important to distinguish between:
public class Calculator {
private int lastResult; // Instance variable
public int add(int a, int b) {
int sum = a + b; // Local variable
lastResult = sum; // Store in instance variable
return sum;
}
}
Here, sum
is a local variable inside the add()
method. It exists temporarily during method execution. lastResult
is an instance variable, maintaining a persistent state across method calls.
Once an object is created, its methods can be invoked using dot notation:
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Alice");
person.greet(); // Output: Hello, my name is Alice
}
}
Here, setName("Alice")
passes a parameter ("Alice"
) to the method, which is then used to update the internal state (name
) of the object.
Java uses pass-by-value for parameter passing:
int
, double
), the value is copied.public class Demo {
public void updateValue(int x) {
x = 10; // Does not affect original variable
}
public void updatePersonName(Person p) {
p.setName("Updated"); // Affects original object
}
}
Calling updateValue()
won’t change the original int
, but updatePersonName()
will update the actual Person
object's name, since the object reference is passed.
public class Person {
// Fields (instance variables)
private String name;
private int age;
// Method with no return value
public void greet() {
System.out.println("Hello, my name is " + name);
}
// Setter for name
public void setName(String newName) {
name = newName;
}
// Getter for name
public String getName() {
return name;
}
// Setter for age
public void setAge(int newAge) {
age = newAge;
}
// Getter for age
public int getAge() {
return age;
}
}
public class Calculator {
private int lastResult; // Instance variable
public int add(int a, int b) {
int sum = a + b; // Local variable
lastResult = sum; // Store in instance variable
return sum;
}
public int getLastResult() {
return lastResult;
}
}
public class Demo {
public void updateValue(int x) {
x = 10; // Does not affect original variable
}
public void updatePersonName(Person p) {
p.setName("Updated"); // Affects original object
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Alice");
person.setAge(30);
person.greet(); // Output: Hello, my name is Alice
Calculator calc = new Calculator();
int result = calc.add(5, 7);
System.out.println("Sum: " + result); // Sum: 12
System.out.println("Last Result: " + calc.getLastResult()); // Last Result: 12
Demo demo = new Demo();
int original = 5;
demo.updateValue(original);
System.out.println("Original after updateValue: " + original); // 5
demo.updatePersonName(person);
System.out.println("Person's name after updatePersonName: " + person.getName()); // Updated
}
}
private
, public
, etc.) control visibility and enforce encapsulation.Understanding and using fields and methods correctly is crucial for designing meaningful and maintainable Java classes. In the next section, you’ll learn about the this
keyword and how it helps distinguish between instance fields and parameters.
this
KeywordIn Java, the keyword this
is a special reference variable that refers to the current object—the instance of the class in which it is used. It is especially useful when differentiating between instance variables (fields) and parameters or local variables with the same name. It also allows a method to return the current object, which can be useful for method chaining and fluent APIs.
A common use case for this
arises in constructors or setter methods where the parameter name is the same as the field name.
this
public class Student {
String name;
public void setName(String name) {
name = name; // This assigns the parameter to itself, not the field!
}
}
In the above example, name = name
does not do what we expect—it just reassigns the method parameter to itself. The field name
remains unchanged.
this
public class Student {
String name;
public void setName(String name) {
this.name = name; // this.name refers to the field, name is the parameter
}
}
Here, this.name
clearly refers to the instance variable, while name
refers to the method parameter. This eliminates ambiguity and ensures the field is correctly updated.
The this
keyword can also be used to return the current object from a method. This technique is often used to enable method chaining—calling multiple methods on the same object in a single statement.
public class Book {
private String title;
public Book setTitle(String title) {
this.title = title;
return this;
}
public void printTitle() {
System.out.println("Title: " + title);
}
}
public class Main {
public static void main(String[] args) {
new Book()
.setTitle("Clean Code")
.printTitle();
}
}
In this example, setTitle()
returns this
, allowing printTitle()
to be chained directly.
this
name = name
) instead of updating the instance field.this
.Using this
is not always required, but it is essential whenever you need to clarify that you're referring to the current object’s field or when enabling fluent programming styles.
The this
keyword is a simple yet powerful tool that helps you:
As your classes grow in complexity, understanding and using this
properly will help make your code clearer and more maintainable.
In Java, a constructor is a special kind of method used to initialize objects when they are created. Unlike regular methods, constructors have the same name as the class and do not have a return type (not even void
). Every time you use the new
keyword to create an object, a constructor is called to set up the object’s initial state.
Understanding how constructors work—and how to write flexible, reusable ones through overloading—is essential to building robust and readable Java classes.
A constructor is executed once when an object is created. It typically sets default or user-defined values for the object's fields.
public class Person {
String name;
int age;
// Constructor
public Person() {
name = "Unknown";
age = 0;
}
}
You can now create a Person
object like this:
Person p1 = new Person(); // name = "Unknown", age = 0
If no constructor is explicitly defined, Java automatically provides a default constructor with no parameters, which does nothing but allow the object to be instantiated.
However, if any constructor is defined, Java does not generate a default one. You must explicitly create it if needed.
public class Animal {
String species;
// No explicit constructor → Java provides a default one.
}
But if you write:
public class Animal {
String species;
public Animal(String s) {
species = s;
}
}
You must now explicitly add a no-argument constructor if you want to create an object like new Animal()
.
A parameterized constructor allows you to pass initial values when creating an object.
public class Car {
String model;
int year;
public Car(String m, int y) {
model = m;
year = y;
}
}
Now you can create a car object like this:
Car car1 = new Car("Toyota", 2020);
Parameterized constructors make your code more expressive and reduce the need to call separate setter methods after object creation.
Java allows you to define multiple constructors in the same class, as long as they differ in the number or types of parameters. This is called constructor overloading, and it lets you create objects in different ways depending on available data.
public class Book {
String title;
String author;
// Default constructor
public Book() {
this("Unknown", "Unknown");
}
// Constructor with one parameter
public Book(String title) {
this(title, "Unknown");
}
// Constructor with two parameters
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public void printInfo() {
System.out.println("Title: " + title + ", Author: " + author);
}
}
Usage:
Book b1 = new Book();
Book b2 = new Book("1984");
Book b3 = new Book("Brave New World", "Aldous Huxley");
b1.printInfo(); // Output: Title: Unknown, Author: Unknown
b2.printInfo(); // Output: Title: 1984, Author: Unknown
b3.printInfo(); // Output: Title: Brave New World, Author: Aldous Huxley
public class Book {
String title;
String author;
// Default constructor
public Book() {
this("Unknown", "Unknown");
}
// Constructor with one parameter
public Book(String title) {
this(title, "Unknown");
}
// Constructor with two parameters
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public void printInfo() {
System.out.println("Title: " + title + ", Author: " + author);
}
}
public class Main {
public static void main(String[] args) {
Book b1 = new Book();
Book b2 = new Book("1984");
Book b3 = new Book("Brave New World", "Aldous Huxley");
b1.printInfo(); // Output: Title: Unknown, Author: Unknown
b2.printInfo(); // Output: Title: 1984, Author: Unknown
b3.printInfo(); // Output: Title: Brave New World, Author: Aldous Huxley
}
}
this()
In the Book
example above, one constructor calls another using the this()
keyword. This is called constructor chaining and helps reduce code duplication.
Rules of constructor chaining:
this()
must be the first statement in the constructor.this()
in a constructor.this
to differentiate parameters from fields when names overlap.Constructors are essential for initializing Java objects. Java supports both default and parameterized constructors, and lets you overload them for flexibility. Constructor chaining using this()
makes your code cleaner and reduces repetition.
As you design more complex classes, mastering constructor overloading and chaining will help ensure objects are created in a consistent, predictable way. In the next section, you’ll explore how Java manages the lifecycle and scope of objects in memory.
Every object in Java goes through a lifecycle: it is created, used, and eventually removed from memory when no longer needed. Understanding this lifecycle—and the different scopes that variables can have—is essential for writing efficient, bug-free code.
Objects in Java are created using the new
keyword. This allocates memory on the heap, where dynamic memory resides:
Car myCar = new Car();
The reference myCar
points to the memory location of the Car
object. Java manages memory using an automatic garbage collector (GC), which periodically reclaims memory used by objects that are no longer reachable.
For example:
Car tempCar = new Car();
tempCar = null; // The object is now eligible for garbage collection
You do not need to (and cannot) manually delete objects in Java. However, understanding when objects become unreachable is important to avoid memory leaks in long-running programs.
Declared inside methods, constructors, or blocks. They exist only during the method's execution.
public void drive() {
int speed = 60; // local variable
System.out.println("Speed: " + speed);
}
Defined in a class but outside any method. They belong to each object.
public class Car {
String model; // instance variable
}
Declared with the static
keyword. They belong to the class itself, not any instance.
public class Car {
static int numberOfCars; // class-level variable
}
public class Example {
int instanceVar = 10;
static int staticVar = 20;
public void methodScope() {
int localVar = 30;
System.out.println(instanceVar); // Allowed
System.out.println(staticVar); // Allowed
System.out.println(localVar); // Allowed
}
public void anotherMethod() {
// System.out.println(localVar); // Error: not visible here
}
}
This illustrates that local variables are not accessible outside their defining method, while instance and static variables are accessible throughout the class.
Java used to provide a finalize()
method, which was called by the garbage collector before reclaiming an object. However, it’s now deprecated and not recommended due to unpredictability and performance concerns.
Instead, use try-with-resources or implement AutoCloseable for releasing non-memory resources (like file handles or network connections).
Java simplifies memory management through automatic garbage collection, ensuring objects are cleaned up when no longer in use. Understanding variable scope—local, instance, and class-level—helps you write clear, maintainable code and avoid common pitfalls such as unintended variable reuse or memory retention.
In later chapters, you’ll learn how this knowledge supports encapsulation, modularity, and efficient resource use in object-oriented design.