# Introduction to Classes & Objects in Java

In Java, **object-oriented programming (OOP)** allows developers to model real-world entities and their interactions. At the heart of OOP lies the **class**. A class serves as a blueprint for creating objects, which encapsulate both state and behavior.

Imagine you’re building a student management system. Instead of writing scattered, unorganized code, you can create modular, reusable components like `Student`, `Course`, and `Teacher` classes. This modularity makes the code easier to maintain, scale, and debug.

By understanding classes and objects, you can write clean and organized Java programs.

### **Definition of a Class**

A **class** in Java is a blueprint for creating objects. It defines the properties (fields) and behaviors (methods) that the objects will possess.

#### **Syntax of a Class**

Here’s the basic structure of a class in Java:

```java
class ClassName {
    // Fields (Instance Variables)
    DataType fieldName;

    // Methods (Actions)
    ReturnType methodName() {
        // Method body
    }
}
```

### **Class Members**

**Class members** are elements defined within a class. These include:

1. **Fields (Instance Variables)**: Represent the state or attributes of an object.
    
2. **Methods**: Define the behavior or actions an object can perform.
    
3. **Access Modifiers**: Control the visibility and accessibility of fields and methods.
    

#### **1\. Fields (Instance Variables)**

Fields are variables that store the state or data of an object.

Example:

```java
String name;
int age;
```

#### **2\. Methods**

Methods perform actions and define the behavior of a class. Methods can return values or perform tasks.

Example:

```java
void introduce() {
    System.out.println("Hi, I'm " + name + " and I'm " + age + " years old.");
}
```

#### **3\. Access Modifiers**

Access modifiers control the visibility of class members. The key access modifiers are:

* `public`: The member can be accessed from any other class.
    
* `private`: The member can only be accessed within the class itself.
    
* `protected`: The member can be accessed within the package and by subclasses.
    
* **Default (Package-Private)**: When no modifier is specified, the member is accessible within the same package.
    

Example:

```java
public String name;
private int age;
String address; // Default access (package-private)
```

### **Creating Instances of a Class**

An **object** is an instance of a class. To create an object, use the `new` keyword, which allocates memory and initializes the object.

#### **Syntax for Creating an Object**

```java
ClassName objectName = new ClassName();
```

## **Code Example with Access Modifiers and File Conventions**

In Java, the **file name must match the** `public` class name. Other classes can reside in the same file, but only one class can be declared `public`.

### **Example Code:**

Create a file named [`Main.java`](http://Main.java) with the following content:

```java
// The public class name matches the file name (Main.java)
public class Main {
    public static void main(String[] args) {
        // Creating instances of the Person class
        Person person1 = new Person();
        person1.name = "Alice";
        person1.age = 25;
        person1.introduce();

        Person person2 = new Person();
        person2.name = "Bob";
        person2.age = 30;
        person2.introduce();
    }
}

// Another class in the same file (non-public)
class Person {
    // Fields with different access modifiers
    public String name;      // Accessible everywhere
    int age;                 // Default access (package-private)

    // Method to introduce the person
    public void introduce() {
        System.out.println("Hi, I'm " + name + " and I'm " + age + " years old.");
    }
}
```

### **Explanation of the Code**

1. **File Name**:  
    The file is named [`Main.java`](http://Main.java) because the `public` class is `Main`.
    
2. **Class Definitions**:
    
    * The `Main` class contains the `main` method, which is the entry point of the program.
        
    * The `Person` class is defined in the same file but is **not declared** `public`, so it is accessible only within the same package.
        
3. **Access Modifiers**:
    
    * The `name` field in the `Person` class is `public`, allowing it to be accessed directly from the `Main` class.
        
    * The `age` field has **default (package-private) access**, meaning it can be accessed from any class within the same package.
        
4. **Creating Objects**:
    
    * Instances of the `Person` class are created using the `new` keyword.
        
    * Fields are assigned values, and the `introduce()` method is called to display the person's information.
        

## **Output**

When you compile and run the program, you will get:

```java
Hi, I'm Alice and I'm 25 years old.
Hi, I'm Bob and I'm 30 years old.
```

## **Key Points to Remember**

1. **Class Structure**: Classes define fields (state) and methods (behavior).
    
2. **Access Modifiers**:
    
    * `public`, `private`, `protected`, and default (package-private).
        
3. **File Naming**:
    
    * The `public` class name must match the file name.
        
    * Multiple non-public classes can exist in the same file.
        
4. **Object Creation**:
    
    * Use the `new` keyword to instantiate objects.
        
5. **Encapsulation**:
    
    * Proper use of access modifiers helps in encapsulating data and maintaining code integrity.
        
    

In Java, the `static` keyword provides a way to create class-level members that are shared across all instances of a class. This is useful when you need to maintain shared data or behavior, such as a counter that tracks the number of instances created. You can access `static` members without creating an instance of the class, which makes them efficient for tasks that don’t rely on object-specific data.

The `final` keyword is used to enforce immutability and prevent changes. Whether you want to declare constants, prevent method overriding, or avoid subclassing, `final` helps maintain integrity in your code.

Together, `static` and `final` play crucial roles in writing efficient and maintainable Java programs.

### **1\. Static Variables and Methods**

#### **Static Variables**

A **static variable** (also known as a class variable) is shared among all instances of a class. When a static variable is modified by one instance, the change is reflected across all other instances.

**Key Points:**

* Declared using the `static` keyword.
    
* Belongs to the class itself, not to any specific instance.
    
* Initialized only once, at the time of class loading.
    

#### **Static Methods**

A **static method** belongs to the class rather than any instance. You can call a static method without creating an instance of the class.

**Key Points:**

* Declared using the `static` keyword.
    
* Can access static variables directly.
    
* Cannot access instance variables or methods unless an instance is explicitly referenced.
    

#### **Example of Static Variables and Methods**

```java
class Counter {
    // Static variable shared by all instances
    static int count = 0;

    // Static method to access the static variable
    static void displayCount() {
        System.out.println("Count: " + count);
    }

    // Instance method to increment the static variable
    void increment() {
        count++;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter obj1 = new Counter();
        Counter obj2 = new Counter();

        obj1.increment();
        obj2.increment();

        // Accessing static method without creating an instance
        Counter.displayCount(); // Output: Count: 2
    }
}
```

#### **Explanation**

1. **Static Variable**:  
    `count` is a static variable that keeps track of the number of increments.
    
2. **Static Method**:  
    `displayCount()` can be called without creating an instance of `Counter`.
    
3. **Shared State**:  
    Both `obj1` and `obj2` modify the same `count` variable.
    

### **2\. Static Blocks**

A **static block** is used for static initialization. It runs **once** when the class is loaded into memory, before any objects are created.

**Key Points:**

* Declared using `static { }`.
    
* Used to initialize static variables or perform setup tasks.
    

#### **Example of Static Blocks**

```java
class Database {
    static String dbName;

    // Static block for initialization
    static {
        dbName = "CustomerDB";
        System.out.println("Static block executed: Database initialized.");
    }

    static void connect() {
        System.out.println("Connecting to " + dbName);
    }
}

public class Main {
    public static void main(String[] args) {
        Database.connect();
    }
}
```

#### **Output**

```java
Static block executed: Database initialized.
Connecting to CustomerDB
```

#### **Explanation**

1. **Static Block**:  
    The static block initializes the `dbName` variable when the class `Database` is first loaded.
    
2. **Single Execution**:  
    The static block runs only once, no matter how many objects are created.
    

### **3\. The** `final` Keyword

The `final` keyword is used to impose restrictions on variables, methods, and classes.

#### **Final with Variables**

* When applied to variables, `final` makes them **constants**.
    
* Once initialized, a `final` variable **cannot be changed**.
    

**Example:**

```java
class Constants {
    final double PI = 3.14159;

    void display() {
        System.out.println("Value of PI: " + PI);
    }
}

public class Main {
    public static void main(String[] args) {
        Constants constants = new Constants();
        constants.display();
    }
}
```

#### **Final with Methods**

* When applied to methods, `final` prevents them from being overridden in subclasses.
    

**Example:**

```java
class Parent {
    final void show() {
        System.out.println("This is a final method.");
    }
}

class Child extends Parent {
    // Error: Cannot override the final method from Parent
    // void show() {
    //     System.out.println("Trying to override.");
    // }
}
```

#### **Final with Classes**

* When applied to classes, `final` prevents them from being subclassed.
    

**Example:**

```java
final class Vehicle {
    void display() {
        System.out.println("This is a vehicle.");
    }
}

// Error: Cannot subclass a final class
// class Car extends Vehicle {
// }
```

## **Comprehensive Example: Combining Static and Final**

```java
class Counter {
    // Static variable
    static int count = 0;

    // Final static constant
    static final int MAX_COUNT = 5;

    // Static block for initialization
    static {
        System.out.println("Static block executed: Counter class loaded.");
    }

    // Method to increment count with a check
    void increment() {
        if (count < MAX_COUNT) {
            count++;
            System.out.println("Count incremented to: " + count);
        } else {
            System.out.println("Maximum count reached: " + MAX_COUNT);
        }
    }

    // Static method to display count
    static void displayCount() {
        System.out.println("Current count: " + count);
    }
}

public class Main {
    public static void main(String[] args) {
        Counter obj1 = new Counter();
        Counter obj2 = new Counter();

        obj1.increment();
        obj2.increment();
        obj1.increment();
        obj2.increment();
        obj1.increment();
        obj2.increment(); // Should reach MAX_COUNT

        Counter.displayCount();
    }
}
```

### **Output**

```java
Static block executed: Counter class loaded.
Count incremented to: 1
Count incremented to: 2
Count incremented to: 3
Count incremented to: 4
Count incremented to: 5
Maximum count reached: 5
Current count: 5
```

#### **Explanation**

1. **Static Variable**:  
    `count` is shared among all instances.
    
2. **Static Constant**:  
    `MAX_COUNT` is a `final static` constant, representing the maximum allowable count.
    
3. **Static Block**:  
    The static block initializes when the `Counter` class is first loaded.
    
4. **Increment Logic**:  
    The `increment()` method checks if `count` has reached `MAX_COUNT`.
    

## **Key Takeaways**

1. **Static Members**:  
    Shared across all instances and can be accessed without creating an object.
    
2. **Static Block**:  
    Runs once when the class is loaded, useful for static initialization.
    
3. **Final Keyword**:
    
    * **Variables**: For constants (immutable values).
        
    * **Methods**: Prevents overriding.
        
    * **Classes**: Prevents subclassing.
        
    

When you create an object in Java, it needs to be initialized with meaningful values. Imagine creating a `Car` object—each car should have its own model and manufacturing year. Rather than setting these properties manually for every object, Java provides a mechanism called a **constructor** to initialize objects at the time of creation.

Constructors help ensure that every object starts in a valid state, either with default values or values provided by the user. This reduces the risk of uninitialized fields and improves code readability and maintainability.

### **Definition of Constructors**

A **constructor** is a special method used to initialize objects. It has the same name as the class and does not have a return type—not even `void`. When an object is created, the constructor is called automatically to set up the initial state of the object.

#### **Key Characteristics of Constructors:**

1. The name of the constructor **matches the class name**.
    
2. It has **no return type**.
    
3. It is called **automatically when an object is created** using the `new` keyword.
    
4. You can have **multiple constructors** in a class (constructor overloading).
    

### **Types of Constructors**

#### **1\. No-Argument Constructor**

A **no-argument constructor** (also called a default constructor) initializes an object with default values. If no constructor is explicitly defined, Java provides a default constructor.

**Syntax:**

```java
class ClassName {
    // No-argument constructor
    ClassName() {
        // Initialization code
    }
}
```

**Example:**

```java
class Car {
    String model;
    int year;

    // No-argument constructor
    Car() {
        model = "Unknown";
        year = 2000;
    }

    void displayInfo() {
        System.out.println(model + " - " + year);
    }
}

public class Main {
    public static void main(String[] args) {
        Car car1 = new Car();
        car1.displayInfo(); // Output: Unknown - 2000
    }
}
```

#### **Explanation:**

* The no-argument constructor initializes `model` to `"Unknown"` and `year` to `2000`.
    
* When `new Car()` is called, this constructor is invoked.
    

#### **2\. Parameterized Constructor**

A **parameterized constructor** allows you to initialize an object with user-specified values. This provides flexibility when creating objects with different initial states.

**Syntax:**

```java
class ClassName {
    // Parameterized constructor
    ClassName(DataType param1, DataType param2) {
        // Initialization code
    }
}
```

**Example:**

```java
class Car {
    String model;
    int year;

    // Parameterized constructor
    Car(String model, int year) {
        this.model = model;
        this.year = year;
    }

    void displayInfo() {
        System.out.println(model + " - " + year);
    }
}

public class Main {
    public static void main(String[] args) {
        Car car2 = new Car("Toyota", 2021);
        car2.displayInfo(); // Output: Toyota - 2021
    }
}
```

#### **Explanation:**

* The parameterized constructor takes `model` and `year` as parameters and initializes the fields accordingly.
    
* The `this` keyword refers to the current object’s fields, differentiating them from the constructor parameters.
    

### **Constructor Overloading**

**Constructor overloading** allows a class to have multiple constructors with different parameter lists. This provides multiple ways to initialize an object.

**Example:**

```java
class Car {
    String model;
    int year;

    // No-argument constructor
    Car() {
        model = "Unknown";
        year = 2000;
    }

    // Parameterized constructor
    Car(String model, int year) {
        this.model = model;
        this.year = year;
    }

    // Another parameterized constructor with one parameter
    Car(String model) {
        this.model = model;
        this.year = 2022; // Default year
    }

    void displayInfo() {
        System.out.println(model + " - " + year);
    }
}

public class Main {
    public static void main(String[] args) {
        Car car1 = new Car();
        Car car2 = new Car("Toyota", 2021);
        Car car3 = new Car("Honda");

        car1.displayInfo(); // Output: Unknown - 2000
        car2.displayInfo(); // Output: Toyota - 2021
        car3.displayInfo(); // Output: Honda - 2022
    }
}
```

#### **Explanation:**

1. **No-Argument Constructor**: Initializes the fields with default values.
    
2. **Parameterized Constructor (Two Parameters)**: Allows specifying both `model` and `year`.
    
3. **Parameterized Constructor (One Parameter)**: Initializes `model` and sets `year` to a default value (`2022`).
    

When creating an object, the appropriate constructor is called based on the arguments passed.

## **Key Points to Remember**

1. **Constructors** are special methods used to initialize objects.
    
2. A **no-argument constructor** provides default initialization.
    
3. A **parameterized constructor** allows custom initialization with user-specified values.
    
4. **Constructor overloading** enables multiple ways to initialize objects within a class.
    
5. **The** `this` Keyword refers to the current object's fields and differentiates them from parameters.
    

## **Benefits of Using Constructors**

1. **Ensures Initialization**: Objects are always initialized properly.
    
2. **Encapsulation**: Keeps object setup logic inside the class.
    
3. **Flexibility**: Constructor overloading allows different initialization scenarios.
    
4. **Readability**: Easier to understand how objects are set up.
    

Imagine you’re writing a `Calculator` class that needs to perform addition. Sometimes, you want to add two integers; other times, you might want to add two floating-point numbers. Instead of creating multiple methods with different names like `addIntegers` and `addDoubles`, wouldn't it be cleaner to have a single method named `add` that works with different types of arguments?

**Method overloading** allows you to define multiple methods with the **same name** but **different parameters**. This makes your code **more readable**, **intuitive**, and **flexible** by allowing methods to handle a variety of input types and numbers of arguments.

### **What is Method Overloading?**

**Method overloading** occurs when multiple methods in the same class have the same name but different parameter lists. The compiler determines which method to call based on the number, type, and order of arguments provided during the method call.

### **Key Rules for Method Overloading**

1. **Same Method Name**: The methods must have the same name.
    
2. **Different Parameters**: The methods must differ in:
    
    * The **number of parameters**, or
        
    * The **type of parameters**, or
        
    * The **order of parameters** (if types are different).
        
3. **Return Type**: The return type **can differ**, but it does not play a role in determining which method to call.
    

## **Examples of Method Overloading**

### **Example 1: Different Parameter Types**

```java
class Calculator {
    // Method to add two integers
    int add(int a, int b) {
        return a + b;
    }

    // Method to add two doubles
    double add(double a, double b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();

        System.out.println("Integer Addition: " + calc.add(5, 3));
        System.out.println("Double Addition: " + calc.add(5.5, 3.2));
    }
}
```

#### **Output**

```java
Integer Addition: 8
Double Addition: 8.7
```

### **Explanation**

* The `add(int a, int b)` method handles integer addition.
    
* The `add(double a, double b)` method handles double addition.
    
* When calling `calc.add(5, 3)`, the compiler selects the method with integer parameters.
    
* When calling `calc.add(5.5, 3.2)`, the compiler selects the method with double parameters.
    

### **Example 2: Different Number of Parameters**

```java
class Printer {
    // Method to print one message
    void print(String message) {
        System.out.println(message);
    }

    // Overloaded method to print a message multiple times
    void print(String message, int times) {
        for (int i = 0; i < times; i++) {
            System.out.println(message);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Printer printer = new Printer();

        printer.print("Hello, World!");
        printer.print("Java is fun!", 3);
    }
}
```

#### **Output**

```java
Hello, World!
Java is fun!
Java is fun!
Java is fun!
```

### **Explanation**

* The `print(String message)` method prints the message once.
    
* The `print(String message, int times)` method prints the message a specified number of times.
    
* When calling `printer.print("Hello, World!")`, the compiler selects the single-parameter method.
    
* When calling `printer.print("Java is fun!", 3)`, the compiler selects the two-parameter method.
    

### **Example 3: Different Parameter Order**

```java
class Display {
    void show(String message, int count) {
        System.out.println("Message: " + message + ", Count: " + count);
    }

    void show(int count, String message) {
        System.out.println("Count: " + count + ", Message: " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        Display display = new Display();

        display.show("Overloading", 2);
        display.show(5, "Method");
    }
}
```

#### **Output**

```java
Message: Overloading, Count: 2
Count: 5, Message: Method
```

### **Explanation**

* The methods `show(String message, int count)` and `show(int count, String message)` have the same name but different parameter orders.
    
* The compiler resolves the correct method based on the argument order in the method call.
    

## **Benefits of Method Overloading**

1. **Improves Code Readability**:  
    You can use the same method name for similar operations, making the code more intuitive.
    
2. **Flexibility**:  
    A single method name can handle different data types or numbers of arguments.
    
3. **Avoids Redundancy**:  
    No need to create multiple methods with different names for similar operations.
    
4. **Enhances Maintainability**:  
    Changes to the method's behavior can be localized to one method name, reducing the risk of errors.
    

## **Key Takeaways**

1. **Method Overloading** allows defining multiple methods with the same name but different parameters.
    
2. The compiler determines which method to call based on the argument types, number, and order.
    
3. Return types do **not** distinguish overloaded methods.
    
4. Overloading enhances code readability, flexibility, and maintainability.
