Lecture 1 / 24
Lecture 01 · Unit-I: Java Programming Fundamentals [CO2]

Introduction, Origin & Features of Java

[CO2] Course Outcome Core Java Language Fundamentals

1. Introduction to Java

Java is a high-level, robust, class-based, object-oriented programming language designed originally by Sun Microsystems (now owned by Oracle Corporation). Java was engineered with a foundational philosophy: “Write Once, Run Anywhere” (WORA). This means compiled Java code can execute on any operating system equipped with a compatible Java Virtual Machine (JVM) without requiring recompilation.

📘 Syllabus Alignment: Stage for Java & Origin
Java was conceived in June 1991 by the Green Team led by James Gosling, Mike Sheridan, and Patrick Naughton at Sun Microsystems. The team initially set out to design a programming language for consumer electronic devices (like smart set-top cable boxes). The language was first called Greentalk (file extension .gt), then renamed to Oak after an oak tree standing outside Gosling's office. Later, in 1995, due to trademark conflicts with Oak Technologies, it was renamed to Java, inspired by Indonesian Java coffee.

2. Challenges Before Java (Why C and C++ Were Insufficient)

In the early 1990s, the computing landscape was dominated by C and C++. However, developing network-centric, cross-platform distributed applications in C/C++ introduced severe challenges:

  • Lack of Portability: C/C++ source code had to be recompiled for each target CPU architecture and operating system due to hardware-dependent primitive data sizes and compiler differences.
  • Manual Memory Management & Leaks: Programmers had to manually allocate and free memory using malloc()/free() or new/delete. Forgetting to deallocate memory caused memory leaks, while premature deallocation created dangerous dangling pointers.
  • Security Vulnerabilities: Raw pointer manipulation allowed unauthorized memory access, buffer overflows, and security exploits across internet-connected devices.
  • Platform-Specific Data Types: An int in C could be 16 bits on one architecture and 32 bits on another, causing logic bugs during porting.

3. The Java Features (The 11 Primary Buzzwords)

The Java language specification details eleven key architectural features (often called Java Buzzwords):

  1. Simple: Java syntax is modeled on C++ (making it easy for existing programmers to adopt), but removes confusing and error-prone features like explicit pointers, operator overloading, multiple inheritance of classes, and header files.
  2. Object-Oriented: In Java, almost everything is an object. Java strictly enforces modular OOP concepts: Abstraction, Encapsulation, Inheritance, and Polymorphism.
  3. Distributed: Designed for network environments with built-in networking APIs (java.net) and Remote Method Invocation (RMI).
  4. Robust: Emphasizes early compile-time error checking, strict runtime type checking, explicit exception handling, and automatic garbage collection.
  5. Secure: Operates inside a secure execution sandbox without pointer arithmetic, verified by the Bytecode Verifier before execution.
  6. Architecture-Neutral: Java compiler compiles code to an intermediate Bytecode format (.class files), independent of target hardware.
  7. Portable: Strict primitive data type specifications (e.g., int is always 32-bit signed two's complement on every CPU on Earth) guarantee identical behavior everywhere.
  8. Interpreted: Bytecode can be quickly executed by any JVM interpreter or translated into native machine instructions on the fly.
  9. High Performance: Through the modern Just-In-Time (JIT) compiler, hot bytecode methods are compiled into native machine code during execution for near-C++ speed.
  10. Multithreaded: Java provides native language-level and library support for concurrent execution through the Thread class and synchronized keyword.
  11. Dynamic: Java programs can dynamically load classes, libraries, and new types at runtime across a local disk or over a network.

4. Testing Java Architecture: Java System Properties

Below is a working Java program that inspects your runtime environment and verifies JVM architecture-neutral properties:

SystemPropertiesCheck.java
public class SystemPropertiesCheck {
    public static void main(String[] args) {
        System.out.println("=== Java Runtime Environment Verification ===");
        System.out.println("Java Version:    " + System.getProperty("java.version"));
        System.out.println("JVM Name:        " + System.getProperty("java.vm.name"));
        System.out.println("OS Architecture: " + System.getProperty("os.arch"));
        System.out.println("OS Name:         " + System.getProperty("os.name"));
        System.out.println("File Separator:  " + System.getProperty("file.separator"));
    }
}
💡 Exam Tip [CO2]
In university examinations, questions frequently ask: “Explain the features of Java that make it secure and robust.” Be prepared to discuss: absence of explicit pointers, automatic garbage collection, strict type verification, and the Java bytecode verifier sandbox.
🎯 Practice Challenge 0.1

Compile and run SystemPropertiesCheck.java. Modify the program to query and print java.vendor and user.home. Observe how Java abstracts OS-level differences behind uniform property keys.

Lecture 02 · Unit-I: Java Programming Fundamentals [CO2]

Java Program Development & OOP Foundations

[CO2] Course Outcome JDK / JRE / JVM OOP Principles

1. The Java Program Development Lifecycle

A Java program moves through several well-defined phases from initial source code creation to machine execution:

  1. Editing: Writing source code in a text file saved with the .java extension (e.g., StudentGrade.java).
  2. Compiling: Invoking the Java compiler (javac StudentGrade.java). The compiler parses the code and produces an intermediate binary file known as Bytecode (StudentGrade.class).
  3. Loading: When executing (java StudentGrade), the ClassLoader loads necessary .class files into JVM memory.
  4. Bytecode Verification: The Bytecode Verifier scans the bytecode to ensure it adheres to JVM security rules and does not perform illegal operations like stack overflows or type violations.
  5. Execution: The JVM execution engine executes the bytecode using an interpreter, dynamically converting frequently executed code paths into native CPU machine code using the Just-In-Time (JIT) Compiler.
📘 JDK vs JRE vs JVM
  • JVM (Java Virtual Machine): The abstract computing machine that executes bytecode. It is platform-dependent (different JVMs exist for Windows, Linux, macOS).
  • JRE (Java Runtime Environment): Contains the JVM plus core class libraries (rt.jar) required to run Java programs.
  • JDK (Java Development Kit): The complete developer software kit containing JRE + development tools (javac, java, javadoc, jar, jdb).

2. Foundations of Object-Oriented Programming (OOP)

Java is an object-oriented language built around six fundamental concepts:

  • Class: A user-defined template or blueprint from which individual objects are created. It encapsulates data fields and method definitions.
  • Object: A runtime instance of a class having a distinct state (values of fields), behavior (methods), and identity.
  • Abstraction: Hiding internal implementation details and exposing only essential functional interfaces to users.
  • Encapsulation: Bundling data and methods operating on that data inside a single unit (class) while restricting direct access using access modifiers (data hiding).
  • Inheritance: The mechanism where a child class acquires the properties and methods of an existing parent class, promoting code reuse.
  • Polymorphism: The ability of an entity (method, operator, or object) to take multiple forms (Compile-time overloading vs Runtime overriding).

3. Anatomical Breakdown of a Java Program

Let us analyze every single keyword in a canonical Java application:

FirstProgram.java
public class FirstProgram {
    public static void main(String[] args) {
        System.out.println("Mastering Java BCA Curriculum");
    }
}
  • public: An access specifier that makes the class accessible from anywhere.
  • class: Keyword used to declare a class.
  • FirstProgram: Identifier defining the class name. If declared public, the file must be named FirstProgram.java.
  • public static void main(String[] args):
    • public: Callable by the JVM from outside the class package.
    • static: Allows the JVM to invoke the method without creating an instance of the class first.
    • void: The entry-point method does not return any value.
    • main: The exact method signature identifier looked up by the JVM execution engine.
    • String[] args: An array of String objects representing command-line arguments.
  • System.out.println(): System is a final class in java.lang, out is a static PrintStream object, and println() is a method that prints text followed by a newline.
🎯 Practice Challenge 1.1

Write a program GreetingApp.java that checks if args.length > 0. If a name is passed via command-line (e.g. java GreetingApp Alice), print Hello, Alice!; otherwise print Hello, Student!.

Lecture 03 · Unit-I: Java Essentials [C2]

Elements of Java Program & Java API

[C2] Course Outcome Java Syntax Java API & Comments

1. Elements of a Java Program

Every Java source file is parsed into foundational lexical tokens:

  • White Space: Spaces, tabs, and newlines that separate tokens. Java compiler ignores superfluous whitespace.
  • Identifiers: User-defined names for classes, methods, variables, and labels.
    • Must start with a letter (A-Z, a-z), underscore (_), or currency symbol ($).
    • Cannot contain spaces or special symbols like @, #, %.
    • Cannot be a reserved Java keyword.
    • Java is case-sensitive: Total, total, and TOTAL are distinct identifiers.
  • Keywords: 50+ reserved words that have predefined meanings to the compiler (e.g. class, public, static, void, if, else, while, try, catch).
  • Literals: Constant values assigned directly to variables (e.g. 100, 3.14, 'A', "Java", true).
  • Separators: Punctuation marks used to structure code:
    • Parentheses (): Delimit parameter lists and expression precedence.
    • Braces {}: Delimit blocks of code, classes, and method bodies.
    • Brackets []: Declare array types and index array elements.
    • Semicolon ;: Terminates statements.
    • Comma ,: Separates declarations and arguments.
    • Period .: Selects package components or class/object members.

2. Overview of Java API (Application Programming Interface)

The Java API is a comprehensive collection of pre-written packages and classes included in the standard Java Development Kit. Key foundational packages include:

Package Name Core Purpose Auto-Imported?
java.lang Fundamental language classes (String, Math, System, Thread, Object, primitive wrappers) Yes (Automatically by default)
java.util Collection framework, date/time, scanner, random numbers, tokenizers No (Requires import java.util.*;)
java.io Input/Output stream processing for files and byte/character streams No (Requires import java.io.*;)
java.net Networking operations, sockets, URLs, HTTP connections No (Requires import java.net.*;)
java.awt Abstract Window Toolkit for GUI rendering and events No (Requires import java.awt.*;)

3. Types of Comments in Java

Java supports three distinct styles of comments:

CommentsAndJavadocDemo.java
/**
 * The Calculator class performs arithmetic operations.
 * <p>This is a <b>Documentation Comment</b> used by the Javadoc utility
 * to generate standard HTML documentation.</p>
 * 
 * @author BCA Student
 * @version 1.0
 */
public class CommentsAndJavadocDemo {

    // 1. Single-line comment: ignores text from // to the end of the line

    /**
     * Calculates the sum of two integers.
     * @param a first operand
     * @param b second operand
     * @return sum of a and b
     */
    public static int add(int a, int b) {
        /* 
           2. Multi-line comment:
           Can span across several lines
           enclosed between forward-slash asterisk and asterisk forward-slash.
        */
        return a + b;
    }

    public static void main(String[] args) {
        int result = add(15, 25);
        System.out.println("Sum: " + result);
    }
}
🎯 Practice Challenge 2.1

Run javadoc CommentsAndJavadocDemo.java in your command prompt. Open the generated index.html in a browser and observe how Java transforms Javadoc comments into official API documentation.

Lecture 04 · Unit-I: Java Essentials [C2]

Variables, Literals, Constants & Primitive Data Types

[C2] Course Outcome Data Types & Ranges Type Casting

1. Variables and Constants

A variable is a named storage location in memory capable of holding data that can change during program execution. A variable has three attributes: a type, a name (identifier), and a value.

  • Declaration: int studentAge;
  • Initialization: studentAge = 20;
  • Dynamic Initialization: Variables can be initialized dynamically using expressions at runtime: double hypotenuse = Math.sqrt(a * a + b * b);
  • Constants: A constant is declared using the final modifier. Once initialized, its value cannot be modified: final double PI = 3.14159265359;

2. The 8 Primitive Data Types in Java

Java is strictly typed. All primitive types have guaranteed, architecture-independent sizes:

Type Category Size (Bits) Range / Values Default
byteInteger8 bits-128 to 1270
shortInteger16 bits-32,768 to 32,7670
intInteger32 bits-2,147,483,648 to 2,147,483,6470
longInteger64 bits-9 quintillion to 9 quintillion (Suffix L)0L
floatFloating-point32 bits~±3.40282347E+38F (Suffix F)0.0f
doubleFloating-point64 bits~±1.7976931348623157E+3080.0d
charCharacter16 bits (Unicode)'\u0000' (0) to '\uffff' (65,535)'\u0000'
booleanLogical1 bit (logical)true or falsefalse

3. Literals in Java

Literals are constant values assigned directly to variables:

  • Integer Literals:
    • Decimal (base 10): 100
    • Hexadecimal (base 16, prefix 0x or 0X): 0x2A (value 42)
    • Octal (base 8, prefix 0): 012 (value 10)
    • Binary (base 2, prefix 0b or 0B, Java 7+): 0b1010 (value 10)
  • Floating-point Literals: Default is double. Float requires F suffix: float f = 12.5f;. Scientific notation: 1.5e-3.
  • Character Literals: Enclosed in single quotes: 'A', '\n' (newline), '\t' (tab), '\u0041'.
  • Boolean Literals: Only true and false (Java does not treat 0 or 1 as boolean).

4. Type Conversion and Casting

Java supports two categories of type conversion:

  • Widening (Implicit / Automatic Casting): Occurs automatically when converting a smaller type to a larger compatible type without precision loss:
    byte → short → int → long → float → double
  • Narrowing (Explicit Casting): Required when converting a larger type to a smaller type or when precision loss can occur. Syntax: (target-type) value.
TypeCastingDemo.java
public class TypeCastingDemo {
    public static void main(String[] args) {
        // Widening Conversion (Automatic)
        int num = 100;
        double d = num; 
        System.out.println("Widened double value: " + d); // prints 100.0

        // Narrowing Conversion (Explicit)
        double pi = 3.14159;
        int truncatedPi = (int) pi;
        System.out.println("Narrowed int value:    " + truncatedPi); // prints 3

        // Byte overflow demonstration
        int largeVal = 130;
        byte b = (byte) largeVal;
        System.out.println("Byte truncated (130 -> byte): " + b); // prints -126 due to wrap-around
    }
}
🎯 Practice Challenge 3.1

Declare variables for a student's roll number (int), grade point average (double), initial (char), and attendance status (boolean). Print all values with descriptive labels.

Lecture 05 · Unit-I: Java Essentials [C2]

The String Class & Operators

[C2] Course Outcome String Immutability Operators & Precedence

1. Introduction to the String Class

Unlike C where strings are null-terminated character arrays, in Java a string is an object of the built-in java.lang.String class. Key characteristics include:

  • Immutability: Once a String object is created in memory, its character sequence cannot be altered. Any operation that appears to modify a string actually creates a brand-new String object.
  • String Constant Pool (SCP): To conserve heap memory, the JVM maintains a special pool for string literals. If two variables share the same literal string, both point to the identical memory address in the pool.
StringCreationDemo.java
public class StringCreationDemo {
    public static void main(String[] args) {
        String s1 = "Java";              // Created in String Constant Pool
        String s2 = "Java";              // Reuses same instance from Pool
        String s3 = new String("Java");  // Forces new instance in regular Heap

        System.out.println(s1 == s2);      // true (points to identical memory address)
        System.out.println(s1 == s3);      // false (different memory addresses)
        System.out.println(s1.equals(s3)); // true (compares actual content characters)
    }
}

2. Operators in Java

Java provides a rich set of operators categorized by function:

  • Arithmetic Operators: + (addition or string concatenation), - (subtraction), * (multiplication), / (division), % (modulus/remainder), ++ (increment), -- (decrement).
  • Relational Operators: ==, !=, >, <, >=, <= (always return a boolean value true or false).
  • Logical Operators: Short-circuit AND (&&), Short-circuit OR (||), Logical NOT (!).
  • Bitwise Operators: & (AND), | (OR), ^ (XOR), ~ (Bitwise unary inversion), << (Signed left shift), >> (Signed right shift), >>> (Unsigned right shift).
  • Assignment Operators: =, +=, -=, *=, /=, %=.
  • Conditional (Ternary) Operator: variable = (condition) ? value_if_true : value_if_false;

3. Complete Operator Evaluation Example

OperatorsCheck.java
public class OperatorsCheck {
    public static void main(String[] args) {
        int a = 10, b = 20;

        // Ternary operator
        int max = (a > b) ? a : b;
        System.out.println("Max: " + max);

        // Bitwise shift
        int val = 8; // binary 00001000
        System.out.println("8 << 1 (8 * 2): " + (val << 1)); // prints 16
        System.out.println("8 >> 1 (8 / 2): " + (val >> 1)); // prints 4
    }
}
🎯 Practice Challenge 4.1

Write an expression using the conditional ternary operator that takes three integer variables x, y, z and finds the largest of the three in a single line.

Lecture 06 · Unit-I: Java Essentials [C2]

Scope of Variables & Blocks

[C2] Course Outcome Variable Lifetime Block Scope

1. Blocks in Java

A block in Java is any group of zero or more statements enclosed within curly braces { ... }. A block can be used wherever a single statement is allowed. Blocks can be nested inside other blocks and define lexical scopes for variables.

2. Scope and Lifetime of Variables

The scope of a variable determines where the variable can be accessed in program source code. The lifetime determines when the variable is allocated memory and when that memory is released:

  • Class/Instance Scope (Member Variables): Declared inside a class but outside methods. Instance variables live as long as the containing object exists on the heap.
  • Method Scope (Parameters & Local Variables): Declared inside a method. Parameters are initialized with argument values when the method is invoked and destroyed when the method returns.
  • Block Scope (Local Block Variables): Declared inside an inner block (such as an if statement or for loop). They are created when the block is entered and destroyed immediately when the closing brace } is reached.
ScopeDemo.java
public class ScopeDemo {
    // Class instance variable (accessible by all non-static methods in this class)
    int instanceVar = 500;

    public void demonstrateScope(int methodParam) {
        // Method local variable
        int x = 10;

        if (x == 10) {
            // Inner block variable
            int y = 20;
            System.out.println("x and y: " + x + " " + y);
            x = y * 2; // Outer variable x is visible inside inner block
        }
        // y = 100; // COMPILATION ERROR! y is unknown here because its scope ended above

        System.out.println("x is now: " + x);
    }

    public static void main(String[] args) {
        ScopeDemo demo = new ScopeDemo();
        demo.demonstrateScope(42);
    }
}
⚠️ Variable Shadowing & Redeclaration Rule
In C/C++, you can declare a variable inside an inner block with the same name as a variable in an outer block, hiding the outer variable. In Java, this is illegal: you cannot declare a local variable in an inner block with the exact same name as a variable in an enclosing outer block. This prevents subtle bugs.
🎯 Practice Challenge 5.1

Analyze this snippet: Can a variable declared in a for (int i = 0; i < 5; i++) header be accessed after the loop terminates? Test and explain your findings using variable block scope rules.

Lecture 07 · Unit-II: Control Statements [CO1]

Decision Making Statements

[CO1] Course Outcome Branching Logic Switch & If-Else

1. Overview of Decision Making in Java

Decision making statements evaluate boolean conditions and alter the sequential flow of program execution based on whether the condition evaluates to true or false.

2. If, If-Else, Nested If, and Else-If Ladder

  • Simple if: Executes a block of code only if the boolean expression is true.
  • if-else: Provides an alternate execution path when the condition evaluates to false.
  • Nested if: An if statement nested inside the body of another if or else block.
  • else-if Ladder: A chain of mutually exclusive conditions evaluated from top to bottom.

3. The Switch Statement

The switch statement tests an expression against a list of constant case values. In Java, switch expressions support: byte, short, char, int, enum types, and String (Java 7+).

DecisionStatementsDemo.java
public class DecisionStatementsDemo {
    public static void main(String[] args) {
        int score = 85;
        char grade;

        // 1. Else-If Ladder
        if (score >= 90) {
            grade = 'A';
        } else if (score >= 80) {
            grade = 'B';
        } else if (score >= 70) {
            grade = 'C';
        } else {
            grade = 'F';
        }
        System.out.println("Student Grade: " + grade);

        // 2. Switch Statement with Strings
        String day = "WEDNESDAY";
        switch (day) {
            case "MONDAY":
                System.out.println("Start of the academic week.");
                break;
            case "WEDNESDAY":
            case "THURSDAY":
                System.out.println("Midweek lecture sessions.");
                break;
            case "FRIDAY":
                System.out.println("Lab practical day.");
                break;
            default:
                System.out.println("Weekend!");
                break;
        }
    }
}
💡 The Fall-Through Behavior
Without a break statement at the end of each case block, execution continues into the subsequent case regardless of whether its test value matches. While sometimes used intentionally to group cases, forgetting break is one of the most common beginner errors in Java exams.
🎯 Practice Challenge 6.1

Implement a simple calculator program using a switch statement that takes two doubles and an operator character ('+', '-', '*', '/') and handles division by zero safely.

Lecture 08 · Unit-II: Control Statements [CO1]

Looping & Jumping Statements

[CO1] Course Outcome Iteration Constructs Break & Continue

1. Looping Statements in Java

Loops allow repeated execution of a block of code as long as a test condition remains true:

  • while Loop (Entry-Controlled): Evaluates the condition before each iteration. If false initially, the body never executes:
    while (condition) { /* body */ }
  • do-while Loop (Exit-Controlled): Evaluates the condition after each iteration. Guarantees that the body executes at least once:
    do { /* body */ } while (condition);
  • for Loop (Count-Controlled): Combines initialization, condition test, and step increment in a compact header:
    for (init; condition; update) { /* body */ }

2. Jumping Statements: Break and Continue

Jumping statements transfer control to another part of the program immediately:

  • break: Exits the enclosing loop or switch immediately.
  • continue: Skips the remainder of the current iteration and jumps to the next cycle.
  • Labeled break & continue: Java does not have a goto statement. Instead, it provides labeled breaks to exit out of multiple nested loops in a single statement!
LoopingAndJumpingDemo.java
public class LoopingAndJumpingDemo {
    public static void main(String[] args) {
        // Labeled break demonstration in nested loops
        System.out.println("=== Searching in Matrix using Labeled Break ===");
        int[][] matrix = {
            {1, 2, 3},
            {4, 99, 6},
            {7, 8, 9}
        };

        int target = 99;
        boolean found = false;

        outerLoop:
        for (int row = 0; row < matrix.length; row++) {
            for (int col = 0; col < matrix[row].length; col++) {
                if (matrix[row][col] == target) {
                    System.out.println("Found " + target + " at row " + row + ", col " + col);
                    found = true;
                    break outerLoop; // Exits both loops directly!
                }
            }
        }
    }
}
🎯 Practice Challenge 7.1

Using nested loops, write a program that prints a Floyd's triangle of numbers up to 5 rows.

Lecture 09 · Unit-II: Classes & Objects [CO1]

Classes, Objects & Modifiers

[CO1] Course Outcome Encapsulation Passing Arguments

1. Basic Concepts of OOPS: Class and Object

A class is a user-defined template or blueprint that defines the structure and behavior of objects. An object is an actual runtime instance created from that class using the new operator.

2. Modifiers in Java

Modifiers are keywords added to declarations to modify their accessibility or behavioral characteristics:

  • Access Modifiers:
    • public: Accessible anywhere across all packages.
    • protected: Accessible within the same package and by subclasses in other packages.
    • default (no keyword): Package-private; accessible only within the same package.
    • private: Accessible only within the declaring class itself (foundation of encapsulation).
  • Non-Access Modifiers: static (belongs to class, not instances), final (cannot be modified/extended), abstract (requires implementation by subclass).

3. Passing Arguments in Java: Pass-by-Value

⚠️ Critical Java Principle: Java is Strictly Pass-by-Value
Java never passes arguments by reference. When an argument is passed to a method:
  • For Primitive Types: An independent copy of the value is passed. Changes made inside the method do not affect the caller's variable.
  • For Objects / References: A copy of the reference address is passed. Both caller and method point to the same object on the heap. Mutating the object's fields affects the caller's object, but reassigning the reference variable itself inside the method has no effect on the caller's reference.
StudentAccount.java
public class StudentAccount {
    // Encapsulated private fields
    private int studentId;
    private String studentName;
    private double gpa;

    // Constructor
    public StudentAccount(int id, String name, double gpa) {
        this.studentId = id;
        this.studentName = name;
        this.gpa = gpa;
    }

    // Getter and Setter methods
    public double getGpa() { return gpa; }
    public void setGpa(double gpa) {
        if (gpa >= 0.0 && gpa <= 10.0) {
            this.gpa = gpa;
        }
    }

    public void displayDetails() {
        System.out.println("ID: " + studentId + " | Name: " + studentName + " | GPA: " + gpa);
    }

    public static void main(String[] args) {
        StudentAccount s1 = new StudentAccount(101, "Gurpreet Singh", 8.7);
        s1.displayDetails();
        s1.setGpa(9.2);
        s1.displayDetails();
    }
}
🎯 Practice Challenge 8.1

Create an Employee class with encapsulated private fields (empId, empName, basicSalary). Add methods to calculate gross salary with 20% HRA and 10% DA.

Lecture 10 · Unit-II: Classes & Objects [CO1]

Constructors, Static Members & GC

[CO1] Course Outcome Constructors & Chaining Garbage Collection

1. Constructors in Java

A constructor is a special member method invoked automatically during object creation to initialize instance variables. It has the exact same name as the enclosing class and does not have any return type (not even void).

  • Default Constructor: If a class provides no constructors, the compiler automatically inserts an invisible, no-argument default constructor.
  • Parameterized Constructor: Takes parameters to initialize custom values for each object.
  • Constructor Overloading: Defining multiple constructors with different parameter signatures in the same class.
  • Constructor Chaining with this(...): Calling one constructor from another constructor within the same class (must be the very first statement in the constructor body).

2. Note on Overloaded Operators in Java

📘 University Syllabus Note: Overloaded Operators in Java
In C++, programmers can define custom operator overloading (e.g., overloading + for matrix or complex number addition). Java deliberately does NOT support user-defined operator overloading to avoid unnecessary complexity and maintain clean, unambiguous syntax. In Java, only the + operator is internally overloaded by the JVM to concatenate strings.

3. Static Class Members

The static keyword declares members that belong to the class itself rather than to individual object instances:

  • Static Variables (Class Variables): Only a single copy exists in memory, shared across all instances of the class.
  • Static Methods: Can be invoked directly via class name (e.g. Math.sqrt()) without instantiating an object. They can only access static members directly.
  • Static Initialization Block: Executed once when the class is first loaded into memory by the ClassLoader.

4. Garbage Collection

Java features Automatic Garbage Collection. When an object on the heap has no live reference variables pointing to it, it becomes eligible for garbage collection. The JVM's background Garbage Collector automatically reclaims the allocated memory, eliminating memory leaks.

StaticAndConstructorDemo.java
public class StaticAndConstructorDemo {
    // Static class variable to count active objects
    private static int objectCount = 0;
    private int id;

    // Default constructor chaining to parameterized constructor
    public StaticAndConstructorDemo() {
        this(1000 + objectCount); // calls overloaded constructor
    }

    // Parameterized constructor
    public StaticAndConstructorDemo(int customId) {
        this.id = customId;
        objectCount++;
    }

    public static int getTotalCreated() {
        return objectCount;
    }

    public static void main(String[] args) {
        StaticAndConstructorDemo obj1 = new StaticAndConstructorDemo();
        StaticAndConstructorDemo obj2 = new StaticAndConstructorDemo(5001);

        System.out.println("Total Objects Created: " + StaticAndConstructorDemo.getTotalCreated());

        // Dereference obj1 so it becomes eligible for Garbage Collection
        obj1 = null;
        System.gc(); // Request JVM to run garbage collector
    }
}
🎯 Practice Challenge 9.1

Write a class BankCustomer with constructors that accept different combinations of parameters (name only, or name + initial deposit). Use this(...) constructor chaining.

Lecture 11 · Unit-II: Inheritance [CO1]

Inheritance Basics & Method Overriding

[CO1] Course Outcome Inheritance Super Keyword

1. Basics of Inheritance

Inheritance is the mechanism by which one class (the subclass or derived class) inherits the fields and methods of another class (the superclass or base class) using the extends keyword. It represents an IS-A relationship and maximizes code reuse.

Java supports:

  • Single Inheritance: Class B extends Class A.
  • Multilevel Inheritance: Class C extends Class B, and Class B extends Class A.
  • Hierarchical Inheritance: Class B and Class C both extend Class A.
⚠️ Why Multiple Inheritance of Classes is Disallowed
Java does not permit a class to extend more than one direct parent class (e.g., class C extends A, B is a compilation error). This avoids the notorious Diamond Problem where duplicate inherited method implementations cause ambiguity. Java solves multiple inheritance cleanly through Interfaces (covered in Unit-III).

2. Inheriting and Overriding Superclass Methods

When a subclass provides its own specific implementation of a method already defined in its superclass, it is called Method Overriding:

  • The method in the subclass must have the exact same method signature (name, parameter list, and return type or covariant subtype).
  • The access modifier cannot be more restrictive than the superclass method.
  • The @Override annotation instructs the compiler to verify overriding validity.

3. The Super Keyword

The super keyword provides a reference to the direct superclass:

  • super(...): Invokes the constructor of the superclass (must be the first line of the subclass constructor).
  • super.methodName(): Calls the overridden version of a method in the superclass.
  • super.variableName: Accesses a superclass field hidden by a subclass field.
InheritanceSuperDemo.java
// Superclass
class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void displayInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

// Subclass
class CollegeStudent extends Person {
    private String course;
    private int rollNumber;

    public CollegeStudent(String name, int age, String course, int roll) {
        super(name, age); // Calling superclass constructor
        this.course = course;
        this.rollNumber = roll;
    }

    @Override
    public void displayInfo() {
        super.displayInfo(); // Reusing superclass method logic
        System.out.println("Roll No: " + rollNumber + ", Course: " + course);
    }
}

public class InheritanceSuperDemo {
    public static void main(String[] args) {
        CollegeStudent st = new CollegeStudent("Aman Deep", 21, "BCA", 108);
        st.displayInfo();
    }
}
🎯 Practice Challenge 10.1

Create a superclass Vehicle with speed and fuel capacity. Create a derived class Car with seating capacity and air-conditioner status. Override a showDetails() method calling super.showDetails().

Lecture 12 · Unit-II: Inheritance & Polymorphism [CO1]

Polymorphism, Abstract & Final Classes

[CO1] Course Outcome Dynamic Method Dispatch Abstract & Final

1. Polymorphism and Dynamic Method Dispatch

Polymorphism is the ability for a call to behave differently based on the runtime type of the object invoked. Java achieves runtime polymorphism through Dynamic Method Dispatch:

When an overridden method is called through a superclass reference variable, Java resolves which version of the method to execute at runtime based on the actual object being referenced, not the type of the reference variable.

2. Abstract Classes and Methods

An abstract class is a class declared with the abstract modifier:

  • It cannot be directly instantiated using new.
  • It can contain both abstract methods (methods with no body, ending in a semicolon) and concrete methods (fully implemented methods).
  • Any non-abstract subclass extending it must provide concrete implementations for all inherited abstract methods.

3. The Final Keyword

The final keyword enforces immutability and finality across three levels:

  • Final Variable: Value cannot be modified after assignment (creates a constant).
  • Final Method: Cannot be overridden by any subclass (prevents altering core logic).
  • Final Class: Cannot be subclassed or inherited (e.g. java.lang.String, java.lang.Math).
PolymorphismAbstractDemo.java
// Abstract base class
abstract class Shape {
    protected String shapeName;

    public Shape(String name) {
        this.shapeName = name;
    }

    // Abstract method (must be implemented by concrete subclasses)
    public abstract double calculateArea();

    // Final concrete method (cannot be overridden by subclasses)
    public final void printShapeType() {
        System.out.println("Shape Type: " + shapeName);
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(double r) {
        super("Circle");
        this.radius = r;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double w, double h) {
        super("Rectangle");
        this.width = w;
        this.height = h;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class PolymorphismAbstractDemo {
    public static void main(String[] args) {
        // Dynamic Method Dispatch using superclass reference
        Shape s1 = new Circle(5.0);
        Shape s2 = new Rectangle(4.0, 6.0);

        s1.printShapeType();
        System.out.println("Area: " + s1.calculateArea());

        s2.printShapeType();
        System.out.println("Area: " + s2.calculateArea());
    }
}
🎯 Practice Challenge 11.1

Create an abstract class BankAccount with an abstract method calculateInterest(). Extend it with SavingsAccount (returns 4% interest) and CurrentAccount (returns 0% interest). Demonstrate polymorphism using an array of BankAccount references.

Lecture 13 · Unit-III: Arrays & Strings

Arrays in Java (1D, 2D & Multidimensional)

Data Structures Multidimensional Arrays Array of Objects

1. Introduction to Arrays in Java

An array is a fixed-size, dynamically allocated collection of homogeneous elements stored in contiguous memory locations. In Java, arrays are objects instantiated on the heap.

  • Declaration: int[] numbers; (preferred) or int numbers[];
  • Instantiation: numbers = new int[5]; (allocates heap memory, initialized to default zeros)
  • Direct Initialization: int[] scores = {95, 88, 72, 90, 85};
  • Length Property: scores.length returns the number of elements (it is a field, not a method).

2. Processing Array Contents & Method Passing

Arrays can be processed sequentially, passed as arguments to methods by reference value, and returned from methods:

ArrayOperations.java
public class ArrayOperations {
    // Passing array as argument
    public static int findMaximum(int[] arr) {
        int max = arr[0];
        for (int val : arr) {
            if (val > max) {
                max = val;
            }
        }
        return max;
    }

    // Returning array from method
    public static int[] reverseArray(int[] arr) {
        int[] rev = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            rev[i] = arr[arr.length - 1 - i];
        }
        return rev;
    }

    public static void main(String[] args) {
        int[] data = {14, 78, 32, 99, 45};
        System.out.println("Maximum element: " + findMaximum(data));
    }
}

3. 2D Arrays, Multidimensional & Jagged Arrays

A two-dimensional array in Java is implemented as an array of arrays. Because of this, rows can have unequal lengths, forming Jagged (or Ragged) arrays:

JaggedArrayDemo.java
public class JaggedArrayDemo {
    public static void main(String[] args) {
        // Declare 2D array with 3 rows but variable columns
        int[][] jagged = new int[3][];
        jagged[0] = new int[2]; // Row 0 has 2 columns
        jagged[1] = new int[4]; // Row 1 has 4 columns
        jagged[2] = new int[3]; // Row 2 has 3 columns

        System.out.println("Row 0 length: " + jagged[0].length);
        System.out.println("Row 1 length: " + jagged[1].length);
    }
}
🎯 Practice Challenge 12.1

Write a program that multiplies two 3x3 matrices and prints the resulting product matrix.

Lecture 14 · Unit-III: Arrays & Strings

Strings, StringBuffer & StringTokenizer

String Manipulation StringBuffer StringTokenizer

1. The String Class Operations

Java's String class provides an extensive suite of built-in methods for textual manipulation:

  • length(): Returns count of UTF-16 code units.
  • charAt(int index): Returns character at specified zero-based index.
  • substring(int begin, int end): Extracts substring from index begin to (end - 1).
  • concat(String str): Appends str to the current string.
  • equals() vs equalsIgnoreCase(): Compares character contents.
  • compareTo(String anotherString): Performs lexicographical comparison.

2. Difference Between String, StringBuffer & StringBuilder

Feature String StringBuffer StringBuilder
Mutability Immutable (cannot modify in place) Mutable (modifies in place) Mutable (modifies in place)
Thread Safety Thread-safe (due to immutability) Thread-safe (Synchronized methods) Not thread-safe (Unsynchronized)
Performance Slow during repeated concatenation Moderate (overhead of locks) Fastest (ideal for single thread)

3. The StringTokenizer Class

Found in java.util, StringTokenizer breaks a string into individual tokens separated by delimiters (default delimiter is whitespace):

StringTokenizerDemo.java
import java.util.StringTokenizer;

public class StringTokenizerDemo {
    public static void main(String[] args) {
        // 1. StringBuffer Demonstration
        StringBuffer sb = new StringBuffer("BCA");
        sb.append(" Punjab Technical University");
        sb.reverse();
        System.out.println("Reversed StringBuffer: " + sb);

        // 2. StringTokenizer Demonstration with CSV string
        String csvData = "RollNo:101,Name:Karanveer,City:Jalandhar,Marks:94";
        StringTokenizer st = new StringTokenizer(csvData, ",");

        System.out.println("Tokens count: " + st.countTokens());
        while (st.hasMoreTokens()) {
            System.out.println("Token: " + st.nextToken());
        }
    }
}
🎯 Practice Challenge 13.1

Write a program that takes an English sentence and uses StringTokenizer to count how many words it contains, and prints each word in reverse order using StringBuffer.

Lecture 15 · Unit-III: Interface and Packages [CO2]

Interfaces & Multiple Inheritance

[CO2] Course Outcome Interfaces Multiple Inheritance

1. Basics of Interface in Java

An Interface is a reference type in Java that serves as a 100% formal contract for classes. It is declared with the interface keyword:

  • All methods declared inside an interface are implicitly public abstract (unless declared default or static in Java 8+).
  • All fields declared in an interface are implicitly public static final (constants).
  • A class implements an interface using the implements keyword and must provide concrete definitions for all methods.

2. Multiple Inheritance Using Interfaces

While Java prohibits multiple class inheritance, a class can implement multiple interfaces simultaneously (e.g. class C implements InterfaceA, InterfaceB). Because interfaces contain method specifications rather than conflicting instance field states, Java completely eliminates the Diamond Problem.

3. Multilevel Interface Inheritance

An interface can extend another interface using extends. An interface can even extend multiple super-interfaces simultaneously:

interface Printable { void print(); }
interface Showable extends Printable { void show(); }
MultipleInterfaceDemo.java
// Interface 1
interface DatabaseEntity {
    void saveToDatabase();
}

// Interface 2
interface Loggable {
    void logOperation(String msg);
}

// Class implementing multiple interfaces
public class MultipleInterfaceDemo implements DatabaseEntity, Loggable {

    @Override
    public void saveToDatabase() {
        System.out.println("Saving student record to MySQL database...");
    }

    @Override
    public void logOperation(String msg) {
        System.out.println("[AUDIT LOG]: " + msg);
    }

    public static void main(String[] args) {
        MultipleInterfaceDemo demo = new MultipleInterfaceDemo();
        demo.logOperation("System boot complete.");
        demo.saveToDatabase();
    }
}
🎯 Practice Challenge 14.1

Define two interfaces: Printable (with print()) and Exportable (with exportToPdf()). Create a class ReportDocument that implements both interfaces.

Lecture 16 · Unit-III: Interface and Packages [CO2]

Packages, Access Specifiers & Static Import

[CO2] Course Outcome Packages & Namespaces Access Specifiers

1. What is a Package?

A package is a grouping mechanism in Java that bundles related classes, interfaces, and sub-packages together. Packages prevent naming collisions and enforce access protection.

  • Declaring a Package: Must be the first statement in the Java file: package com.ptu.bca;
  • Compiling with Package Hierarchy: javac -d . Student.java (the -d . flag directs javac to automatically create the folder directory hierarchy matching package names).
  • Importing Packages: import com.ptu.bca.Student; or import com.ptu.bca.*;

2. Static Import

Introduced in Java 5, static import allows members defined in a class as static to be used in another class without specifying the class name prefix:

StaticImportDemo.java
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;

public class StaticImportDemo {
    public static void main(String[] args) {
        // Direct access without prefixing Math.
        double area = PI * pow(5.0, 2);
        double diagonal = sqrt(25.0);
        System.out.println("Circle Area: " + area);
        System.out.println("Square Root: " + diagonal);
    }
}

3. The Comprehensive Access Specifiers Matrix

Java provides four levels of visibility:

Access Specifier Same Class Same Package (Subclass / Non-subclass) Different Package (Subclass) Different Package (World)
public Yes Yes Yes Yes
protected Yes Yes Yes (via inheritance) No
default (package-private) Yes Yes No No
private Yes No No No
🎯 Practice Challenge 15.1

Create a package university.department with a class Faculty containing protected double salary. In another package university.staff, create a subclass Professor that accesses salary through inheritance.

Lecture 17 · Unit-III: Exception Handling [CO5]

Exception Handling — Try, Catch & Throw

[CO5] Course Outcome Robust Execution Try / Catch / Throw

1. Introduction to Exception Handling

An exception is an abnormal event or error condition that occurs during program execution and disrupts the normal flow of instructions. Java represents exceptions as objects inheriting from the java.lang.Throwable hierarchy:

  • Error: Serious system conditions that normal applications should not attempt to catch (e.g., OutOfMemoryError, StackOverflowError).
  • Exception:
    • Checked Exceptions: Subclasses of Exception (excluding RuntimeException). Checked at compile-time. The compiler insists that the programmer handle them using try-catch or declare them with throws (e.g. IOException, SQLException).
    • Unchecked Exceptions: Subclasses of RuntimeException. Checked at runtime. Usually result from programming logic flaws (e.g. NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException).

2. Try, Catch, Multiple Catch & Nested Try

  • try Block: Encloses code that might throw an exception.
  • catch Block: Handles the specific exception if thrown inside the associated try block.
  • Multiple Catch: A single try block can be followed by multiple catch blocks. Rule: Catch handlers must be arranged from most specific (subclass) to most generic (superclass).
  • Nested Try: A try block placed inside another try block. If an inner try lacks a matching catch handler, the exception bubbles up to the outer try-catch.

3. The Throw Statement

The throw keyword explicitly throws an exception instance in code:

ExceptionDemo.java
public class ExceptionDemo {
    public static void validateStudentAge(int age) {
        if (age < 18) {
            // Explicitly throw an unchecked exception
            throw new IllegalArgumentException("Student must be at least 18 years old for admission.");
        }
        System.out.println("Admission eligibility verified for age " + age);
    }

    public static void main(String[] args) {
        try {
            // Nested try block
            try {
                int result = 50 / 0;
            } catch (ArithmeticException e) {
                System.out.println("[Inner Catch] Caught division by zero: " + e.getMessage());
            }

            validateStudentAge(16); // throws exception

        } catch (IllegalArgumentException e) {
            System.out.println("[Outer Catch] Caught validation error: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("[Outer Generic Catch]: " + e.getMessage());
        }
    }
}
🎯 Practice Challenge 16.1

Write a program with an array of 5 integers. Read an index from the user. Use multiple catch blocks to catch both ArrayIndexOutOfBoundsException and NumberFormatException.

Lecture 18 · Unit-III: Exception Handling [CO5]

Finally Block & Built-In Exceptions

[CO5] Course Outcome Finally Guarantee Custom Exceptions

1. The Finally Block

The finally block creates a block of code that is guaranteed to execute, regardless of whether an exception was thrown or caught inside the try block. It is primarily used for releasing external system resources (closing file streams, database connections, socket handles).

💡 When Does Finally NOT Execute?
The finally block executes even if the try block contains a return statement! The only scenario where finally does not execute is if System.exit(0) is invoked, or in the catastrophic event of a fatal JVM crash / power loss.

2. Common Built-In Exceptions in Java

Exception Class Type Trigger Condition
ArithmeticException Unchecked Arithmetic errors, such as integer division by zero
ArrayIndexOutOfBoundsException Unchecked Accessing an array with an illegal negative or ≥ length index
NullPointerException Unchecked Attempting to invoke a method or access a field on a null reference
NumberFormatException Unchecked Failed conversion of a string to numeric format via Integer.parseInt()
ClassNotFoundException Checked ClassLoader cannot locate the specified .class file
IOException Checked Failure during input/output stream operations (disk failure, file missing)

3. Creating Custom / User-Defined Exceptions

A programmer can create domain-specific exceptions by extending java.lang.Exception:

CustomExceptionDemo.java
// Custom Checked Exception
class InsufficientBalanceException extends Exception {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

public class CustomExceptionDemo {
    private static double balance = 5000.00;

    public static void withdraw(double amount) throws InsufficientBalanceException {
        System.out.println("Attempting withdrawal of: Rs " + amount);
        if (amount > balance) {
            throw new InsufficientBalanceException("Deficit balance! Available: Rs " + balance);
        }
        balance -= amount;
        System.out.println("Withdrawal successful. Remaining: Rs " + balance);
    }

    public static void main(String[] args) {
        try {
            withdraw(6000.00);
        } catch (InsufficientBalanceException e) {
            System.err.println("Transaction Failed: " + e.getMessage());
        } finally {
            System.out.println("[Finally] Session logging closed. Receipt printed.");
        }
    }
}
🎯 Practice Challenge 17.1

Create a custom checked exception InvalidAgeForVotingException. Write a method that checks a voter's age and throws this exception if age is less than 18. Demonstrate handling with a finally block.

Lecture 19 · Unit-IV: Multithreading [CO4]

Multithreading — Creation & Lifecycle

[CO4] Course Outcome Concurrent Programming Thread Lifecycle

1. Introduction to Multithreading

Multitasking is the ability of an operating system to execute more than one task concurrently. Java supports two forms of multitasking:

  • Process-based Multitasking (Multiprocessing): Running multiple independent programs simultaneously (e.g., running Java compiler and a web browser at the same time). Processes have separate memory spaces.
  • Thread-based Multitasking (Multithreading): Running multiple sub-tasks (threads) concurrently within the same application program. Threads share the same address space, resulting in minimal context-switching overhead and maximum CPU core utilization.

2. Thread Creation in Java

Java provides two primary approaches for creating threads:

Approach A: Extending the Thread Class

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Child thread running via Thread class extension.");
    }
}

Approach B: Implementing the Runnable Interface (Recommended)

Because Java does not permit multiple class inheritance, implementing Runnable is preferred because your class remains free to inherit from another parent class:

ThreadCreationDemo.java
// Implementing Runnable interface
class BackgroundWorker implements Runnable {
    private String taskName;

    public BackgroundWorker(String name) {
        this.taskName = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(taskName + " executing step " + i);
            try {
                Thread.sleep(500); // Pause thread for 500 milliseconds
            } catch (InterruptedException e) {
                System.err.println(taskName + " interrupted.");
            }
        }
    }
}

public class ThreadCreationDemo {
    public static void main(String[] args) {
        // Instantiate Runnable workers
        Thread t1 = new Thread(new BackgroundWorker("Download-Worker"));
        Thread t2 = new Thread(new BackgroundWorker("Audio-Worker"));

        // start() triggers JVM to allocate thread stack and call run()
        t1.start();
        t2.start();
    }
}

3. The Lifecycle of a Thread (Thread States)

A Java thread transitions through distinct states during its lifespan:

  1. New: A thread has been instantiated (new Thread()), but start() has not yet been invoked.
  2. Runnable: After start() is called, the thread is eligible for CPU execution and waits in the ready queue.
  3. Running: The thread scheduler selects the thread from the queue and executes its run() method on a CPU core.
  4. Blocked / Waiting / Timed-Waiting: Temporarily inactive because it is waiting for a monitor lock, sleeping (sleep()), or waiting on another thread (join(), wait()).
  5. Terminated (Dead): The thread's run() method has completed execution.
🎯 Practice Challenge 18.1

Write a program with two threads: one thread prints even numbers from 2 to 20, and the other thread prints odd numbers from 1 to 19 concurrently.

Lecture 20 · Unit-IV: Multithreading [CO4]

Thread Management & Synchronization

[CO4] Course Outcome Thread Synchronization Thread Priority & Join

1. Joining a Thread

The join() method allows one thread to pause execution and wait until another target thread has completed its execution. This guarantees deterministic ordering of interdependent background tasks.

2. Thread Scheduler & Priorities

The Thread Scheduler in the JVM determines which runnable thread gets CPU execution time. Every Java thread has a priority represented by an integer from 1 to 10:

  • Thread.MIN_PRIORITY = 1
  • Thread.NORM_PRIORITY = 5 (default)
  • Thread.MAX_PRIORITY = 10
  • Method: t.setPriority(int priority);

3. Thread Synchronization: Preventing Race Conditions

When multiple threads attempt to access and modify the same shared mutable resource concurrently, data corruption occurs (known as a Race Condition). Java solves this using the synchronized keyword, which acquires an exclusive mutual-exclusion monitor lock on an object:

  • Synchronized Method: public synchronized void deposit(double amount) { ... }
  • Synchronized Block: synchronized(sharedResource) { /* critical section */ }
BankSynchronizationDemo.java
// Shared account resource
class JointAccount {
    private int balance = 1000;

    // Synchronized method prevents race condition between threads
    public synchronized void withdraw(String user, int amount) {
        if (balance >= amount) {
            System.out.println(user + " is preparing to withdraw " + amount);
            try {
                Thread.sleep(100); // Simulate network latency
            } catch (InterruptedException e) {}
            balance -= amount;
            System.out.println(user + " completed withdrawal. New Balance: " + balance);
        } else {
            System.out.println("Insufficient funds for " + user);
        }
    }
}

public class BankSynchronizationDemo {
    public static void main(String[] args) throws InterruptedException {
        JointAccount account = new JointAccount();

        Thread t1 = new Thread(() -> account.withdraw("Simran", 700));
        Thread t2 = new Thread(() -> account.withdraw("Rahul", 700));

        t1.start();
        t2.start();

        // Wait for both threads to finish
        t1.join();
        t2.join();
        System.out.println("All transactions processed cleanly.");
    }
}
🎯 Practice Challenge 19.1

Explain the role of wait(), notify(), and notifyAll() in inter-thread communication. Write a producer-consumer buffer example where the producer waits when the buffer is full and notifies the consumer.

Lecture 21 · Unit-IV: Applets [CO4]

Applets & Applet Life Cycle

[CO4] Course Outcome Applet Architecture Life Cycle Methods

1. Introduction to Applets

An Applet is a special type of Java program designed to be embedded within an HTML webpage and executed inside a Java-enabled web browser or via the appletviewer utility. Applets were foundational in establishing Java's early popularity on the World Wide Web.

📘 Standalone Applications vs. Applets
  • Standalone Applications: Have a main() method, are started directly by the JVM, and possess unrestricted access to local system resources (file systems, network ports).
  • Applets: Do not have a main() method. Execution is controlled by browser life-cycle callbacks. For security, Applets run inside a restricted Sandbox and cannot read/write arbitrary local files or execute native programs without signed permissions.

2. The Applet Class Hierarchy

The standard Applet class belongs to the package java.applet.Applet. Its inheritance chain is:

java.lang.Object
  → java.awt.Component
      → java.awt.Container
          → java.awt.Panel
              → java.applet.Applet

3. The Applet Life Cycle Methods

The JVM / Applet container manages an applet through five lifecycle methods:

  1. public void init(): Invoked only once when the applet is first loaded into memory. Used for one-time initialization (creating GUI components, loading images, reading parameters).
  2. public void start(): Invoked after init(), and every time the user navigates back to the webpage. Starts or resumes execution threads.
  3. public void paint(Graphics g): Invoked automatically whenever the applet's display surface must be redrawn (e.g. after minimizing/restoring window). Used for rendering graphics, text, and geometric shapes.
  4. public void stop(): Invoked whenever the user navigates away from the webpage. Suspends active background animations or threads.
  5. public void destroy(): Invoked once before the applet is completely unloaded from memory. Cleans up non-memory system resources.

4. Embedding an Applet in HTML

WelcomeApplet.java
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Color;

/*
<applet code="WelcomeApplet.class" width="400" height="200">
    <param name="university" value="I.K. Gujral PTU">
</applet>
*/
public class WelcomeApplet extends Applet {
    private String universityName;

    @Override
    public void init() {
        setBackground(Color.LIGHT_GRAY);
        universityName = getParameter("university");
        if (universityName == null) {
            universityName = "Punjab Technical University";
        }
        System.out.println("Applet initialized via init().");
    }

    @Override
    public void paint(Graphics g) {
        g.setColor(Color.BLUE);
        g.drawString("Welcome to BCA Java Programming!", 50, 80);
        g.setColor(Color.DARK_GRAY);
        g.drawString("Institution: " + universityName, 50, 120);
    }
}
🎯 Practice Challenge 20.1

List the order of execution of applet lifecycle methods when an applet is loaded, minimized, restored, and closed. Run appletviewer WelcomeApplet.java to test the applet.

Lecture 22 · Unit-IV: Applets & Events [CO4]

Graphics in Applets & Event Handling

[CO4] Course Outcome AWT Graphics Delegation Event Model

1. The Graphics Class in Applets

Rendering in Applets is driven by the java.awt.Graphics class. The coordinate system places (0,0) at the top-left corner of the applet panel, with x increasing horizontally to the right and y increasing vertically downwards.

Common graphics drawing methods:

  • drawLine(int x1, int y1, int x2, int y2)
  • drawRect(int x, int y, int width, int height) and fillRect(...)
  • drawOval(int x, int y, int width, int height) and fillOval(...)
  • drawRoundRect(x, y, w, h, arcWidth, arcHeight)
  • drawString(String str, int x, int y)
  • setColor(Color c) and setFont(Font f)

2. The Delegation Event Model

Java's GUI event handling follows the Delegation Event Model, which consists of three key components:

  1. Event Source: A GUI component that generates an event when interacted with (e.g. Button, TextField, Applet window).
  2. Event Object: Encapsulates details about what occurred (e.g. ActionEvent, MouseEvent, KeyEvent).
  3. Event Listener: An interface defining callback methods that handle the event (e.g. ActionListener, MouseListener). The listener is registered with the source using source.addActionListener(listener).
InteractiveAppletDemo.java
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

/*
<applet code="InteractiveAppletDemo.class" width="400" height="300">
</applet>
*/
public class InteractiveAppletDemo extends Applet implements ActionListener {
    private Button redBtn, blueBtn;
    private Color currentColor = Color.WHITE;

    @Override
    public void init() {
        redBtn = new Button("Red Canvas");
        blueBtn = new Button("Blue Canvas");

        // Register this class as event listener
        redBtn.addActionListener(this);
        blueBtn.addActionListener(this);

        add(redBtn);
        add(blueBtn);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == redBtn) {
            currentColor = Color.RED;
        } else if (e.getSource() == blueBtn) {
            currentColor = Color.BLUE;
        }
        repaint(); // Requests JVM to invoke paint()
    }

    @Override
    public void paint(Graphics g) {
        g.setColor(currentColor);
        g.fillOval(100, 80, 200, 150);
        g.setColor(Color.BLACK);
        g.drawString("Click buttons to change oval color", 80, 260);
    }
}
🎯 Practice Challenge 21.1

Implement a MouseMotionListener in an Applet that draws coordinates (x, y) wherever the user moves the mouse cursor on the canvas.

Lecture 23 · Unit-IV: File and I/O Streams [CO4]

File Class, Streams & Byte Streams

[CO4] Course Outcome File Handling Byte Streams

1. The Concept of Streams in Java

A Stream in Java I/O represents a continuous, ordered sequence of data flowing from a source (producer) to a destination (consumer). Java streams are unidirectional:

  • Input Stream: Reads data from a source (file, memory buffer, socket).
  • Output Stream: Writes data to a destination.

2. The File Class

The java.io.File class represents file and directory pathnames. It does not read or write data directly, but inspects and manipulates file metadata:

  • exists(): Checks if file/directory exists.
  • isFile() / isDirectory(): Determines file type.
  • length(): Returns file size in bytes.
  • createNewFile(): Creates empty file on disk.
  • delete(): Deletes file/directory.
  • list(): Lists files in a directory as a string array.

3. Byte Streams (8-Bit Streams)

Byte Streams read and write binary data 8 bits at a time. The root abstract classes are InputStream and OutputStream.

Key Byte Stream Classes:

  • FileInputStream / FileOutputStream: Direct byte read/write from disk files.
  • Filtered Byte Streams: Wrappers that add functionality to raw streams:
    • BufferedInputStream / BufferedOutputStream: Buffers data in memory for high-speed I/O.
    • DataInputStream / DataOutputStream: Reads and writes primitive Java data types (readInt(), writeDouble()) in binary format.
ByteStreamCopyDemo.java
import java.io.*;

public class ByteStreamCopyDemo {
    public static void copyFileBinary(File src, File dest) throws IOException {
        // Using try-with-resources for automatic stream closure
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(src));
             BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {

            byte[] buffer = new byte[1024];
            int bytesRead;

            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("File copied successfully: " + dest.getName());
        }
    }

    public static void main(String[] args) {
        File sourceFile = new File("sample.dat");
        File destFile = new File("sample_copy.dat");
        try {
            copyFileBinary(sourceFile, destFile);
        } catch (IOException e) {
            System.out.println("I/O Exception handled: " + e.getMessage());
        }
    }
}
🎯 Practice Challenge 22.1

Write a program that uses DataOutputStream to write five student roll numbers and percentages into a file students.bin, and reads them back with DataInputStream.

Lecture 24 · Unit-IV: File and I/O Streams [CO4]

Character Streams & Random Access Files

[CO4] Course Outcome Character Streams RandomAccessFile

1. Character Streams (16-Bit Unicode Streams)

While Byte Streams operate on 8-bit bytes, Character Streams are specifically designed to handle 16-bit Unicode characters. This ensures seamless internationalization and character-set encoding translation (e.g. UTF-8). The root abstract classes are Reader and Writer.

Key Character Stream Classes:

  • FileReader / FileWriter: Direct character reading/writing for text files.
  • BufferedReader: Buffers characters and provides the indispensable readLine() method.
  • BufferedWriter: Buffers output and provides newLine() for platform-independent line breaks.
  • PrintWriter: Provides convenient formatted printing (print(), println(), printf()).

2. Random Access File Class

Normal stream classes can only access files sequentially from start to finish. The java.io.RandomAccessFile class permits reading and writing anywhere inside a file at arbitrary offsets (non-sequential access).

  • Modes: "r" (read-only), "rw" (read and write).
  • File Pointer: A cursor that tracks the current byte position in the file.
  • seek(long position): Moves the file pointer to the specified byte offset.
  • getFilePointer(): Returns current byte offset.
CharacterAndRandomAccessDemo.java
import java.io.*;

public class CharacterAndRandomAccessDemo {
    public static void main(String[] args) {
        File file = new File("bca_records.txt");

        // 1. Write text using PrintWriter & FileWriter
        try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
            pw.println("BCA-101: Aman Kumar");
            pw.println("BCA-102: Priya Sharma");
            pw.println("BCA-103: Rajat Verma");
            System.out.println("Records written via PrintWriter.");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 2. Read with BufferedReader line by line
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String line;
            System.out.println("=== Reading Student Records ===");
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 3. RandomAccessFile Demonstration
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
            raf.seek(9); // Jump to character position 9
            System.out.println("Byte pointer at: " + raf.getFilePointer());
            raf.writeBytes("Deepak"); // Overwrite characters in place
            System.out.println("Record updated in place via RandomAccessFile seek.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
🎯 Practice Challenge 23.1

Write a program that uses BufferedReader to read a Java source file and prints each line with its line number prefixed (e.g. 1: public class ...).