Java OOP Tutorial
Class and Object
Definition:
A class is a blueprint for creating objects. It defines attributes (fields) and methods (functions).
An object is an instance of a class.
public class Person {
// fields/attributes
private String name;
protected int age;
// Default constructor (optional unless required)
public Person() {
[Link] = "Unknown"; // this refers to the current object
[Link] = 0;
}
// Constructor with parameters
public Person(String name, int age) {
[Link] = name;
[Link] = age;
}
// Getters and Setters to access or modify attributes
public String getName() { return name; }
public void setName(String name) { [Link] = name; }
public int getAge() { return age; }
public void setAge(int age) { [Link] = age; }
// Method
public void introduce() {
[Link]("Hi, I'm %s and I'm %d years old.\n", name, age);
}
// Main method that creates two Person objects
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
[Link](); // [Link]()
[Link]();
}
}
1
Notes:
In a Java project, only one class needs to contain the main method, which serves as
the entry point of the application.
Attributes are variables declared inside a class to represent the state of an object.
A constructor is a special method that is called when an object is created. It initializes
the object’s fields. You can have multiple constructors in a class.
A method is like a function in Java. It usually works on a specific object’s data — for
example, [Link]() calls the introduce() method on the p1 object.
Encapsulation and Visibility (public, private, protected)
Definition:
Encapsulation restricts direct access to class members (fields), exposing them only through
public methods (getters/setters). This improves data safety. Encapsulation hides the details of
how a class works and only allowing access through special methods called getters and setters.
For example, you can’t directly use [Link] if name is private — you must use [Link]()
or [Link]("Alice").
This helps protect the data and makes it easier to control how values are read or changed.
When your project has many classes in different packages, encapsulation also controls which
parts of the code can be used from other classes and which parts stay hidden.
Visibility Modifiers:
private: Accessible only within the class.
public: Accessible from anywhere.
protected: Accessible within the same package or subclasses.
Default (no modifier): Accessible within the same package.
In the class Person, the fields are declared with restricted access: name is private and age is
protected, which means they cannot be accessed directly from outside the class using
syntax like [Link] or [Link].
For example, the following code is not allowed and will cause a compilation error:
Person p1 = new Person();
// [Link] = "Alice"; // ❌ Error: name has private access
// [Link]([Link]); // ❌ Error: name has private access
To access or modify these fields, the class must provide getter and setter methods like this:
Person p1 = new Person();
[Link]("Alice");
[Link](25);
[Link]([Link]()); // ✅ Output: Alice
[Link]([Link]()); // ✅ Output: 25
2
Static Members and Static Methods
Definition:
Static fields (also called class variables) are shared by all instances of a class. There's
only one copy in memory, no matter how many objects you create.
Static methods belong to the class, not to a specific object.
A static method can only access other static members (fields or methods) directly.
It cannot directly access instance fields or methods because it doesn't belong to any
object, it does not have access to this. It is called using the class name.
Example:
public class Person {
private String name;
private int age;
// Static field shared by all Person objects
private static int count = 0;
// Constructor
public Person(String name, int age) {
[Link] = name;
[Link] = age;
count++; // Increase count whenever a new Person is created
}
// Static method to display total number of Person objects
public static void showPersonCount() {
[Link]("Total persons created: " + count);
}
}
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
Person p3 = new Person("Charlie", 28);
// Call the static method to show total number of Person objects
[Link](); // Output: Total persons created: 3
}
User Input and Output Formatting
User Input
Java uses the Scanner class (from [Link] package) to read input from the user via the
keyboard. First you create a Scanner object then you call of the following methods to read an
integer, a string, a double, or a line.
import [Link].*;
// Create a scanner on the standard input which is the keyboard
Scanner input = new Scanner([Link]);
// Read an integer
int age = [Link]();
3
Common methods of Scanner:
Method Type Read
nextInt() Integer
nextDouble() Double
nextLine() Whole line (String)
next() Single word (String)
nextFloat() Float
nextBoolean() Boolean
Output Formatting
Java uses:
[Link]() → prints without newline.
[Link]() → prints with newline.
[Link]() → formatted output (like C's printf).
Format Specifiers for printf():
Format Description
%d Integer (decimal)
%f Floating point number
%.2f Floating number with 2 decimal places
%s String
%n Newline (platform-independent)
Example with Input and Output Formatting:
import [Link];
public class InputOutputExample {
public static void main(String[] args) {
Scanner input = new Scanner([Link]);
[Link]("Enter product name: ");
String name = [Link]();
[Link]("Enter quantity: ");
int quantity = [Link]();
[Link]("Enter unit price: ");
double price = [Link]();
double total = quantity * price;
// Formatted Output
[Link]("Product: %s%n", name);
[Link]("Quantity: %d, Unit Price: %.2f%n", quantity,
price);
[Link]("Total Cost: %.2f%n", total); } }
4
Type Conversion (Casting)
Implicit Conversion (Widening)
Happens automatically when converting from a smaller to a larger type.
int a = 10;
double b = a; // int to double (OK)
Explicit Conversion (Narrowing)
Requires casting when converting from a larger to a smaller type.
double x = 9.7;
int y = (int) x; // double to int (y = 9, decimal part lost)
String Conversion (to and from other types)
🔸 To String:
int a = 25;
String s = [Link](a); // "25"
🔸 From String:
String s = "123";
int n = [Link](s); // 123
double d = [Link](s); // 123.0
Example: Type Conversion in Action
public class ConversionExample {
public static void main(String[] args) {
String ageStr = "30";
int age = [Link](ageStr); // Convert String to int
double average = age / 2.0; // int divided by double
String message = "Half of " + age + " is " + average;
[Link](message);
}
Inheritance
Definition:
Inheritance allows a class to inherit fields (attributes) and methods (behaviors) from another
class by using the keyword extends. It lets a subclass (child class) gain access to the members
of a superclass (parent class), enabling code reuse and logical hierarchy.
5
Inheritance creates a natural "is-a" relationship (Student is a Person).
Key points about inheritance in Java:
A class can inherit only one class (single inheritance) using extends.
However, a class can implement multiple interfaces using implements.
public class Student extends Person implements Learner, Athlete {
// can extend only one class (Person)
// but can implement many interfaces (Learner, Athlete)
}
Why use inheritance?
The superclass is generic, representing common features.
The subclass is more specialized, adding or modifying features.
Example:
1. Person class: generic, with name and age.
2. Student class: specialized, adds studentId.
Accessing superclass members
The subclass cannot directly access private attributes of the superclass.
It must use public or protected getters and setters to access private members.
Public and protected members can be accessed directly by the subclass.
Calling superclass methods from subclass Person
- name:String
Use super to call methods or constructors of the superclass. - age:int
+Person(name, age)
Example: +introduce()
+introduce(string)
public class Student extends Person {
private String studentId;
extends
public Student(String name, int age, String studentId) {
super(name, age); // calls constructor of Person
[Link] = studentId;
Student
}
- studentId: String
public void showPersonInfo() { +Student(name, age,
[Link](); // call superclass method studentId)
[Link]("Student ID: " + +introduce()
studentId); +introduce(string)
}
}
6
Difference Between Overloading and Overriding
Overloading (Compile-time polymorphism)
Occurs within the same class.
Multiple methods share the same name but have different parameter lists (different
number or types of parameters).
Return type can be the same or different.
Purpose: To provide several ways to perform a similar action depending on the input.
Example idea:
A method introduce() with no parameters, and another introduce(String greeting) that takes
a greeting.
Overriding (Runtime polymorphism)
Happens when a subclass redefines a method inherited from its superclass.
The method in the subclass has the same name, return type, and parameters as in
the superclass.
Purpose: To change or extend the behavior of the superclass method for the subclass.
You can call the superclass method inside the overridden one using super.
Now, the example with Person and Student classes showing both concepts:
public class Person {
protected String name;
protected int age;
public Person(String name, int age) {
[Link] = name;
[Link] = age;
}
// Original introduce method
public void introduce() {
[Link]("Hi, I'm %s and I'm %d years old.\n", name, age);
}
// Overloaded introduce method (same name, different parameters)
public void introduce(String greeting) {
[Link]("%s, I'm %s and I'm %d years old.\n", greeting,
name, age);
}
}
public class Student extends Person { // Define a subclass
private String studentId;
// Constructor
public Student(String name, int age, String studentId) {
super(name, age);
[Link] = studentId;
}
7
// Overriding the introduce method to add student ID info
@Override
public void introduce() {
[Link](); // Call superclass introduce
[Link]("My student ID is %s.\n", studentId);
}
// Overloaded introduce method with greeting
public void introduce(String greeting) {
[Link]("%s, I'm %s, %d years old, and my student ID is
%s.\n", greeting, name, age, studentId);
}
}
How the methods behave?
Suppose you create these objects:
Person p = new Person("Alice", 40);
Student s = new Student("Bob", 20, "S12345");
1. Calling introduce() on Person object:
[Link](); // Calls the original method in Person
// Output: Hi, I'm Alice and I'm 40 years old.
2. Calling overloaded method introduce(String greeting) on Person:
[Link]("Hello"); /* Calls the overloaded method in Person (same
name, different parameters) */
// Output: Hello, I'm Alice and I'm 40 years old.
3. Calling introduce() on Student object:
[Link](); /* Calls the overridden method in Student
Inside it, it calls [Link]() (the Person version) and then adds
the Student ID info. */
/* Output: Hi, I'm Bob and I'm 20 years old.
My Student ID is S12345 */
4. Calling overloaded method introduce(String greeting) on Student:
[Link]("Good morning"); //Calls the overloaded method in Student
// Output: Good morning, I'm Bod, 20 years old, and my student ID is S12345
8
Polymorphism in Java
Definition:
Polymorphism means “many forms” — it allows one interface or reference type to represent
different underlying forms (actual types) of objects.
In Java, polymorphism mainly refers to the ability of a superclass reference to point to
objects of subclasses, enabling methods to behave differently based on the actual object type.
Reference type vs Actual type
Reference type: The type used when declaring a variable (e.g., Person p).
Actual type (object type): The real class of the object created with new (e.g., new
Student(...)).
Example: Person p = new Student("Bob", 20, "S12345");
Reference type = Person
Actual type = Student
The variable p is declared as type Person, but it actually refers to a Student object.
What does polymorphism affect?
At compile time, the compiler checks methods and variables based on the reference
type (Person).
At runtime, the JVM calls the method based on the actual type of the object (Student),
if the method is overridden.
Upcasting and Downcasting
Upcasting (automatic)
Casting a subclass object to a superclass reference.
Happens automatically — no explicit cast needed.
Example:
Student s = new Student("Bob", 20, "S12345");
Person p = s; // upcasting: Student → Person
Useful because you can treat a Student as a Person.
9
Downcasting (explicit)
Casting a superclass reference back to a subclass reference.
Requires an explicit cast.
Can fail at runtime if the object is not really of the subclass type.
Example:
Person p = new Student("Bob", 20, "S12345");
Student s = (Student) p; // downcasting: Person → Student
Use downcasting when you need to access subclass-specific methods or fields.
Example with polymorphism, upcasting, and downcasting
public class Person {
protected String name;
protected int age;
public Person(String name, int age) {
[Link] = name;
[Link] = age;
}
public void introduce() {
[Link]("Hi, I'm %s and I'm %d years old.\n", name, age);
}
}
public class Student extends Person {
private String studentId;
public Student(String name, int age, String studentId) {
super(name, age);
[Link] = studentId;
}
@Override
public void introduce() {
[Link]();
[Link]("My student ID is %s.\n", studentId);
}
// Method proper to Student class
public void study() {
[Link](name + " is studying.");
}
}
public class Main {
public static void main(String[] args) {
// Upcasting: Student object referenced as Person
Person p = new Student("Bob", 20, "S12345");
// Polymorphism: calls Student's overridden method
[Link]();
10
// [Link](); // ERROR! study() not visible for Person reference
// Downcasting: cast back to Student to call study()
if (p instanceof Student) {
Student s = (Student) p;
[Link]();
}
} // End main()
}
Why use polymorphism?
To write flexible and reusable code that can work with objects of different classes through
a common interface (superclass or interface).
o Imagine you have many kinds of people: Student, Teacher, Employee — all
subclasses of Person. Instead of writing separate code for each, you can write code
that works with any Person, regardless of the specific type.
o Example:
public void welcomePerson(Person p) {
[Link](); /* Calls the correct version depending on
actual object type */
}
/* Now you can pass any subclass object, the method adapts
dynamically, thanks to polymorphism */
welcomePerson(new Student(...));
welcomePerson(new Teacher(...));
welcomePerson(new Employee(...));
Achieve dynamic behavior (runtime method selection)
o This allow method calls to automatically invoke the appropriate subclass
implementation, enabling dynamic behavior.
o Using a superclass reference lets the program decide at runtime which method to call
based on the actual object type, not just the reference type.
o This enables:
Extensibility — you can add new subclasses without changing existing code.
Code maintainability — common code handles many cases elegantly.
Collections of heterogeneous objects
You can store different subclass objects in a single collection typed by the superclass:
List<Person> people = new ArrayList<>();
[Link](new Student(...));
[Link](new Teacher(...));
[Link](new Employee(...));
for (Person p : people)
[Link](); // Polymorphism in action
11
Arrays in Java: Fixed-Size Collections
An Array is a fixed-size container that stores multiple elements of the same type.
Can hold primitives or objects
Indexed access with array[index]
Main Methods/Properties:
o [Link] (size)
o Access elements by index: array[i]
Example:
Person[] people = new Person[2];
people[0] = new Person("Alice", 30);
people[1] = new Student("Bob", 20, "S123");
for (int i = 0; i < [Link]; i++) {
[Link](people[i].getName() + " - Age: " +
people[i].getAge());
}
ArrayLists
An ArrayList is a resizable array, part of Java’s Collections Framework defined in
([Link] package), which can dynamically grow and shrink as elements are added or
removed. ArrayLists can only store objects, not primitive types directly. You can use their
wrapper classes instead like ArrayList<Integer> numbers = new ArrayList<>();
Main Methods:
o add(E e) — add element to the end
o get(int index) — get element at index
o remove(int index) or remove(Object o) — remove elements
o size() — number of elements
o contains(Object o) — check if element exists
Example:
import [Link];
ArrayList<Person> peopleList = new ArrayList<>();
[Link](new Person("Alice", 30));
[Link](new Student("Bob", 20, "S123"));
for (Person p : peopleList) {
[Link]([Link]() + " - Age: " + [Link]());
}
for (int i = 0; i < [Link](); i++) {
Person p = [Link](i);
[Link]([Link]() + " - Age: " + [Link]()); }
12
ArrayList<Student> students = new ArrayList<>();
[Link](new Student("Bob", 22, "S002"));
[Link](); // returns 1
[Link](0); // get first
[Link](0); // remove
[Link](...); // test if present
HashMaps: Key-Value Data Structures
A HashMap stores data as key-value pairs. Each key is unique and is associated with a
value, enabling fast retrieval of values based on their keys. This data structure is useful
when you want to quickly find an object based on some identifier.
Common methods:
o put(K key, V value) — add or update a pair
o get(Object key) — get value by key
o remove(Object key) — remove a pair
o containsKey(Object key) — check if key exists
Example:
import [Link];
HashMap<String, Person> map = new HashMap<>();
[Link]("person1", new Person("Alice", 30));
[Link]("student1", new Student("Bob", 20, "S123"));
Person p = [Link]("student1");
[Link]([Link]() + " - Age: " + [Link]());
Iterators: Traversing Collections Safely
An Iterator provides a way to traverse elements in a collection (like ArrayList,
HashMap’s values, etc.) one at a time without exposing the underlying structure.
Main Methods:
o hasNext() — checks if more elements exist
o next() — returns the next element
o remove() — removes the last element returned by next()
Example:
ArrayList<Person> peopleList = new ArrayList<>(); // fill it
Iterator<Person> iterator = [Link]();
while ([Link]()) {
Person p = [Link]();
[Link]([Link]() + " - Age: " + [Link]());}
13
Association (Composition and Aggregation) in Java
In Java, association represents a relationship between two classes. It defines how objects of
one class are connected to objects of another class. Association can be of two main types:
Aggregation: A "has-a" relationship where the child can exist independently of the
parent.
Composition: A "strong has-a" relationship where the child cannot exist independently
of the parent.
Inheritance vs Association
Inheritance (IS-A): One class inherits from another. For example, Student is a
Person (i.e., Student extends Person).
➤ It defines what an object is.
Association (HAS-A): One class uses another. For example, a School has many
Students.
➤ It defines what an object has.
📌 Inheritance and association are independent concepts. You can use both together, and
they often complement each other.
Notes:
Inheritance (IS-A) is used for specialization (Student is a Person).
Association (HAS-A) is used to show ownership or usage (School has Students,
School has Principal).
Aggregation allows shared or reused parts. Ex: A School has many Students.
Composition implies exclusive ownership. Ex: A School has one Principal.
Aggregation Example: School and Students
School has a list of Student objects.
But the Student objects can exist independently of the School. For example, you
can create Student objects before the School or use them elsewhere.
import [Link];
public class School {
private String schoolName;
private List<Student> students; // aggregation
public School(String schoolName, List<Student> students) {
[Link] = schoolName;
[Link] = students;
}
14
public void showStudents() {
for (Student s : students) {
[Link]([Link]);
} } }
Composition Example: School and Principal
The Principal is created inside the School.
It cannot exist outside or without the School — hence, composition.
public class Principal {
private String name;
public Principal(String name) {
[Link] = name;
}
}
public class School {
private String schoolName;
private Principal principal; // composition
public School(String schoolName, String principalName) {
[Link] = schoolName;
[Link] = new Principal(principalName);
}
}
More Examples:
1. Aggregation: Course and Students
Students can be part of multiple courses — they are shared objects.
public class Course {
private String title;
private List<Student> enrolledStudents;
public Course(String title, List<Student> students) {
[Link] = title;
[Link] = students;
}
}
2. Composition: Classroom and Whiteboard
The Whiteboard is completely dependent on the Classroom.
15
public class Whiteboard {
private String size;
public Whiteboard(String size) {
[Link] = size;
}
}
public class Classroom {
private Whiteboard whiteboard;
public Classroom(String boardSize) {
[Link] = new Whiteboard(boardSize); // tightly bound
}
}
Types of Classes in Java
1. Concrete Class
A regular class with full implementation of its methods. You can create objects of this class
directly.
public class Person {
protected String name;
protected int age;
public Person(String name, int age) {
[Link] = name;
[Link] = age;
}
public void displayInfo() {
[Link]("Name: " + name + ", Age: " + age);
}
}
2. Abstract Class
An abstract class is a class that is declared with the abstract keyword. It may or may not
contain abstract methods.
An abstract class cannot be instantiated (we cannot create objects of it). It can contain both:
Abstract methods (without body)
Concrete methods (with body)
It is used to provide a common base class for other classes.
16
public abstract class Person {
protected String name;
protected int age;
public Person(String name, int age) {
[Link] = name;
[Link] = age;
}
// Abstract method (must be implemented by subclasses)
public abstract void displayInfo();
// Concrete method
public void greet() {
[Link]("Hello, my name is " + name);
}
}
Example: Using Abstract Class with Student, so class Student must implement (define) the
abstract method void displayInfo() of class Person.
public class Student extends Person {
private String studentId;
public Student(String name, int age, String studentId) {
super(name, age);
[Link] = studentId;
}
@Override
public void displayInfo() {
[Link]("Student Name: " + name + ", Age: " + age + ",
ID: " + studentId);
}}
3. Interface
An interface defines a contract of methods that a class must implement. All methods in an
interface are public and abstract by default.
A class can implement multiple interfaces
Interfaces have no constructors and no instance fields
You can define constants in a Java interface but discouraged in modern Java. They
must be public static final. Accessible from anywhere, belongs to the interface, not to
instances, and cannot be changed after assignment.
Example1:
public interface MathConstants {
double PI = 3.14159; // implicitly public static final
int MAX_SCORE = 100;
}
17
public static void main(String[] args) {
[Link]("Value of PI: " + [Link]);
[Link]("Max Score: " + MathConstants.MAX_SCORE); }
Example2: Implementing Interface with Person and Student
public interface Displayable {
void displayInfo(); // method to be implemented by classes
}
public class Person implements Displayable {
protected String name;
protected int age;
public Person(String name, int age) {
[Link] = name;
[Link] = age;
}
@Override
public void displayInfo() {
[Link]("Person: " + name + ", Age: " + age);
}
}
public class Student extends Person {
private String studentId;
public Student(String name, int age, String studentId) {
super(name, age);
[Link] = studentId;
}
@Override
public void displayInfo() {
[Link]("Student: " + name + ", Age: " + age + ", ID:
" + studentId);
}
}
✅ Real-Life Analogy
Abstract class: Think of Vehicle as an abstract class. All vehicles have wheels and
engines, but each type (Car, Bike, Truck) implements start() differently. They all inherit
basic structure.
Interface: Think of Drivable or Flyable as interfaces. A Car, Drone, and Airplane can
all be "Drivable" or "Flyable", even if they aren't related by inheritance.
✅ Summary in 2 Lines
Use abstract classes to share code and enforce structure among related classes.
Use interfaces to enforce behavior across unrelated classes and support multiple
inheritance of types.
18
4. Outer Class
The outer class is simply a regular top-level class that contains other classes or
members.
It is the main class that may contain inner classes or be referenced by them.
Example:
public class OuterClass {
int outerField = 10;
// Inner class defined inside OuterClass
class InnerClass {
void display() {
[Link]("Outer field is " + outerField);
}
}
}
5. Inner Class (Non-static Nested Class)
An inner class is a class defined inside another class (the outer class).
It can access the members (including private) of the outer class.
Used to logically group classes and to access outer class members conveniently.
Requires an instance of the outer class to be instantiated.
Example:
public class OuterClass {
int outerField = 10;
class InnerClass {
void display() {
[Link]("Outer field is " + outerField);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
[Link] inner = [Link] InnerClass();
[Link](); // Prints: Outer field is 10
}
}
6. Anonymous Class (Unnamed classes created on the fly)
An anonymous class is a one-time, unnamed class declared and instantiated all at
once.
It usually extends a class or implements an interface.
19
Used for quick implementation of interfaces or subclasses without creating a separate
named class.
Great for event handling, callbacks, or quick custom behavior.
Example of an anonymous class that implements the Comparator interface to sort a list of
strings by their last character
public static void main(String[] args) {
List<String> words = [Link]("apple", "banana", "cherry",
"date");
// Anonymous class to compare strings by their last character
Comparator<String> byLastChar = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
char lastChar1 = [Link]([Link]() - 1);
char lastChar2 = [Link]([Link]() - 1);
return [Link](lastChar1, lastChar2);
}
}; // End of anonymous class definition
[Link](words, byLastChar);
[Link](words); // Output: [banana, apple, date, cherry]
}
Lambda Expressions in Java
A lambda expression introduced in Java 8, is an anonymous function used to pass a
small piece of code (behavior) as a parameter to methods or to implement a single-
method interface quickly and simply.
General syntax is either (parameters) -> expression or (parameters) -> { statements; }
It lets you write code inline without creating a full class.
It is mostly used to replace simple anonymous classes.
It helps you make your code shorter and easier to read.
Example1: Print each item in a list
import [Link];
import [Link];
public class SimpleLambda1 {
public static void main(String[] args) {
List<String> fruits = [Link]("Apple", "Banana", "Cherry");
// Print each fruit using lambda
[Link](fruit -> [Link](fruit));
}
}
20
Example2: Add two numbers (using a functional interface)
interface MathOperation {
int operate(int a, int b);
}
public class SimpleLambda2 {
public static void main(String[] args) {
// Lambda to add two numbers
MathOperation add = (a, b) -> a + b;
[Link]("5 + 3 = " + [Link](5, 3));
}
}
Example3:
import [Link];
import [Link];
public class LambdaCombinedExample {
public static void main(String[] args) {
// Lambda to check if a number is even
Predicate<Integer> isEven = n -> n % 2 == 0;
// Lambda to convert a string to uppercase
Function<String, String> toUpperCase = s -> [Link]();
int number = 10;
String text = "hello";
[Link](number+ "is even? " +[Link](number));// true
[Link]("Uppercase: " +[Link](text)); // HELLO
} }
21
[Link]