0% found this document useful (0 votes)
6 views

Java_SubjectSummaryNotes

The document provides an overview of Java, emphasizing its object-oriented programming (OOP) paradigm, which organizes software as collections of objects that encapsulate data and behavior. It covers key concepts such as abstraction, encapsulation, inheritance, polymorphism, and the use of blocks of code, as well as lexical issues, primitive data types, variables, type conversion, arrays, and operators. Additionally, it introduces local variable type inference with the 'var' keyword, enhancing coding efficiency while maintaining type safety.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

Java_SubjectSummaryNotes

The document provides an overview of Java, emphasizing its object-oriented programming (OOP) paradigm, which organizes software as collections of objects that encapsulate data and behavior. It covers key concepts such as abstraction, encapsulation, inheritance, polymorphism, and the use of blocks of code, as well as lexical issues, primitive data types, variables, type conversion, arrays, and operators. Additionally, it introduces local variable type inference with the 'var' keyword, enhancing coding efficiency while maintaining type safety.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 43

Module – 1

1. An Overview of Java: Object-Oriented Programming

Java is a high‐level, robust, and secure programming language designed from the start with object-
oriented programming (OOP) in mind. In Java, software is modeled as a collection of “objects” that
bundle together data (fields) and behavior (methods). This approach contrasts with procedural
programming, where code is organized as a sequence of instructions or functions.
Two Paradigms in Java

1. Procedural (Procedure-Oriented) Programming:


o In procedural programming, the focus is on writing sequences of instructions or
functions that operate on data.
o Data often exists in global variables or is passed around between functions, and
the program’s flow is controlled by these functions.
o This paradigm can be efficient for small or straightforward tasks but may lead to
issues such as data inconsistency in larger systems.
2. Object-Oriented Programming (OOP):
o OOP organizes code around objects, which encapsulate both data and behavior.
o Key advantages include improved modularity, reusability, and maintainability by
bundling related functionality together.
o This paradigm better mirrors real-world entities and relationships, making it ideal
for complex applications.

The contrast between these paradigms highlights why Java’s OOP approach is so valuable for
creating large, scalable systems.
Abstraction in Java

Abstraction is the process of distilling complex reality into simplified models by exposing only the
relevant features and hiding unnecessary details. In Java, abstraction is achieved by:

• Abstract Classes and Interfaces:


You can define an abstract class (using the abstract keyword) or an interface that declares
methods without specifying full implementations. Concrete classes then implement these
methods with details specific to their context.
• Focus on “What” Rather Than “How”:
Users of a class only need to know its public methods and properties (the interface) without
understanding the underlying implementation. This simplifies both usage and maintenance.

For example, an abstract class Animal may declare a method makeSound(), and different subclasses
like Dog and Cat provide their own implementations.

The Three OOP Principles

While many sources list four fundamental pillars (abstraction, encapsulation, inheritance, and
polymorphism), when abstraction is treated as a broader concept, the three core principles often
emphasized are:

1. Encapsulation:
o Encapsulation binds data (attributes) and methods (behavior) together within a
class and restricts direct access to some of an object’s components.
o By using access modifiers (private, protected, public), a class can hide its
internal state and require all interaction to occur through well-defined methods
(getters/setters).
o This leads to more secure and maintainable code.
2. Inheritance:
o Inheritance allows a new class (child or subclass) to acquire the properties and
behaviors (fields and methods) of an existing class (parent or superclass).
o It promotes code reuse and establishes a natural hierarchical relationship.
o For example, a Dog class can inherit from a general Animal class, reusing common
behaviors while adding its own specific features.
3. Polymorphism:
o Polymorphism enables objects of different classes to be treated as objects of a
common superclass.
o It allows the same method to behave differently on different classes via method
overriding or overloading.
o This flexibility is key when writing code that is independent of specific object
types, enhancing reusability and scalability.

Together, these principles help build robust, modular, and scalable systems by managing complexity
through clear structure and well-defined interfaces.

2. Using Blocks of Code

In Java, a block of code is a group of zero or more statements enclosed by curly braces { }. Blocks
define a scope—a region in the code where variables and methods are accessible. They are used in
several contexts:

• Class and Method Bodies:


Every class and method has its body defined by a pair of braces. For example:
public class MyClass {
public void myMethod() {
// Code block: statements inside the method
System.out.println("Hello, World!");
}
}

• Control Structures:
Loops (for, while), conditional statements (if, else), and even try-catch blocks use braces to
group statements.
if (condition) {
// Block executed if condition is true
doSomething();
} else {
// Block executed if condition is false
doSomethingElse();
}

• Nested Blocks:
You can define nested blocks within a method to create new local scopes. Variables declared
inside a nested block are not accessible outside of it.
public void example() {
int a = 10;
{
int b = 20; // 'b' is only available in this inner block
System.out.println(a + b);
}
// System.out.println(b); // This would cause a compile error
}

Blocks of code are essential in Java because they provide the structure and scope control necessary
for organized, readable, and maintainable programs.

3. Lexical Issues in Java

Lexical issues refer to the basic elements (or tokens) of the Java programming language. These are the
smallest units that the compiler recognizes. Key elements include:
a. Whitespace

• Definition:
Whitespace consists of spaces, tabs, and newline characters.
• Role:
It is used solely to separate tokens. Java is a “free-form” language, meaning that the
amount of whitespace does not affect the execution of the code (except within string
literals).
• Example:
The following two statements are equivalent:
int a = 5;
int a=5;

Whitespace improves readability but has no effect on how the program runs.
b. Identifiers

• Definition:
Identifiers are names given to elements such as classes, methods, and variables.
• Rules:
o Must start with a letter, underscore (_), or dollar sign ($).
o Subsequent characters can be letters, digits, underscores, or dollar signs.
o Identifiers are case-sensitive (e.g., Value and value are different).
• Examples:
Valid: myVariable, _temp, $result
Invalid: 2ndValue, high-temp

These rules ensure that each identifier is uniquely and clearly defined in the program.
c. Literals

• Definition:
Literals represent fixed values directly written into the code.
• Types:
o Integer Literals: e.g., 100, 0xFF
o Floating-Point Literals: e.g., 3.14, 2.5e3
o Character Literals: e.g., 'A', '\n'
o String Literals: e.g., "Hello, World!"
o Boolean Literals: true and false
Literals are used wherever constant values are needed.
d. Comments

• Purpose:
Comments are used to explain code, making it easier to understand. They are ignored by
the compiler.
• Types:
o Single-line Comments: Start with // and continue until the end of the line.
// This is a single-line comment.
o Multi-line Comments: Enclosed between /* and */.
/* This is a
multi-line comment. */
o Documentation Comments (Javadoc): Start with /** and end with */. They are
used to generate HTML documentation.
/**
* This method prints a greeting message.
*/
public void greet() { ... }

Proper use of comments helps document the purpose and functionality of code sections.
e. Separators

• Definition:
Separators (or punctuators) are special characters that separate tokens in the code.
• Common Separators:
o Parentheses: ( and )
o Braces: { and }
o Brackets: [ and ]
o Semicolon: ; (used to terminate statements)
o Comma: , (used to separate items in lists)
o Period: . (used for member access)

These symbols help structure the code and define its grammar.
f. Java Keywords

• Definition:
Keywords are reserved words that have a special meaning in Java. They cannot be used
as identifiers.
• Examples:
class, public, static, if, else, while, for, new, etc.
• Purpose:
They form the core syntax of the language.

Using keywords correctly is crucial since they define the language’s structure.

1. Primitive Data Types

Java defines a set of built-in, or primitive, data types. They represent the most basic kinds of data and
are not objects. The four main categories include:
a. Integers

Java provides several integer types, each with a different range and storage size:

• byte:
o 8-bit signed integer
o Range: -128 to 127
o Useful for saving memory in large arrays.
• short:
o 16-bit signed integer
o Range: -32,768 to 32,767
o Used when you need a wider range than byte but still want to conserve space.
• int:
o 32-bit signed integer
o Range: -2³¹ to 2³¹-1
o The most commonly used integer type.
• long:
o 64-bit signed integer
o Range: -2⁶³ to 2⁶³-1
o Used when a wider range than int is needed.
o A literal can be marked as a long by appending L (e.g., 12345678901L).

b. Floating-Point Types

Floating-point types represent numbers with fractional parts:

• float:
o 32-bit IEEE 754 floating-point
o Less precise; you need to append f or F to the literal (e.g., 3.14f).
• double:
o 64-bit IEEE 754 floating-point
o More precise and is the default for floating-point literals (e.g., 3.14159).

c. Characters

• char:
o 16-bit Unicode character
o Represents a single character (e.g., 'A', '%', or even Unicode symbols like
'\u03A9' for Ω).

d. Booleans

• boolean:
o Represents one of two values: true or false
o Often used for conditions and flags.

Primitive types are stored directly in memory and are much faster to access compared to objects.
2. Variables

Variables are named storage locations for data. In Java, you must declare a variable with a specific
type before you can use it. Here’s what you need to know:

• Declaration and Initialization:


You declare a variable by specifying its type and name, and optionally initialize it with a value.
int count; // Declaration
count = 35; // Initialization
int total = 100; // Declaration with initialization

• Scope and Lifetime:


The scope of a variable is the region of the code where it can be accessed. For example, a
variable declared inside a method is local to that method and ceases to exist once the method
finishes executing.
• Naming Conventions:
Variable names (identifiers) should start with a letter, underscore (_), or dollar sign ($) and are
case-sensitive.

3. Type Conversion and Casting

Java supports converting values from one type to another. There are two forms:
a. Implicit Conversion (Widening)

• Definition:
When you convert a value from a smaller type to a larger type (e.g., from int to long), Java
does it automatically.
• Example:
int num = 100;
long bigNum = num; // Implicit conversion from int to long

b. Explicit Conversion (Narrowing/Casting)

• Definition:
Converting from a larger type to a smaller type may lose information, so you must
explicitly cast it.
• Example:
double precise = 3.14159;
int approx = (int) precise; // Cast double to int, result is 3

• Caution:
When casting, data loss may occur (e.g., truncating decimals) and overflow/underflow
might result if the value is outside the range of the target type.

4. Automatic Type Promotion in Expressions

When performing arithmetic or other operations, Java may automatically promote (or convert) values
to a common type to avoid loss of precision:

• Promotion Rules:
obyte, short, and char are automatically promoted to int when used in arithmetic
expressions.
o If one operand is a long, the entire expression is promoted to long.
o Similarly, if one operand is float or double, the result is promoted accordingly.
• Example:
byte a = 40;
byte b = 50;
// a and b are promoted to int before multiplication:
int result = a * b; // Result is of type int

// Combining int with long:


int i = 1000;
long l = 5000L;
long sum = i + l; // i is promoted to long, result is long

This automatic promotion ensures that arithmetic operations are performed with a sufficient level of
precision.

5. Arrays

Arrays are a way to store multiple values of the same type in a single variable. Key points include:

• Declaration and Initialization:


You declare an array by specifying the type followed by square brackets [] and then the
variable name.
int[] numbers; // Declaration of an int array
numbers = new int[5]; // Allocation for 5 integers
// Or combine declaration and allocation:
int[] values = new int[]{1, 2, 3, 4, 5};

• Fixed Size:
Once an array is created, its size cannot be changed. You can, however, reassign the variable to
a new array.
• Accessing Elements:
Array elements are accessed via their index (starting from 0).
int first = values[0]; // Access first element
values[2] = 10; // Modify the third element

• Multidimensional Arrays:
Java supports arrays of arrays (e.g., 2D arrays) where you can declare and initialize as follows:
int[][] matrix = new int[3][4]; // A 3x4 matrix
matrix[1][2] = 7; // Set element at row 1, column 2

Arrays are a fundamental data structure in Java, used extensively for storing collections of data.

6. Introducing Type Inference with Local Variables

Starting with Java 10, the language supports local variable type inference using the var keyword.
This allows the compiler to determine the variable's type based on the initializer, simplifying code
without sacrificing type safety.

• Usage Example:
var message = "Hello, Java 10!"; // Inferred as String
var count = 42; // Inferred as int
var list = new ArrayList<String>(); // Inferred as ArrayList<String>
• Benefits:
o Conciseness: Reduces redundancy by eliminating explicit type declarations when
the type is obvious.
o Readability: Helps focus on the variable’s purpose rather than its type.
• Limitations:
o Local Variables Only: Type inference with var is allowed only for local variables,
not for class fields, method parameters, or return types.
o Initializer Requirement: A variable declared with var must be initialized at the
time of declaration.

Type inference with local variables is a modern feature that increases coding efficiency while keeping
code strongly typed.

Operators in Java define the actions to be performed on operands (values or variables). They are the
building blocks for constructing expressions. Below is a detailed explanation of various operator
categories and related concepts:

1. Arithmetic Operators

Arithmetic operators are used to perform mathematical calculations. The primary arithmetic operators
are:

• Addition (+):
Adds two operands.
int sum = 5 + 3; // sum is 8
• Subtraction (-):
Subtracts the second operand from the first.
int difference = 10 - 4; // difference is 6
• Multiplication (*):
Multiplies two operands.
int product = 7 * 3; // product is 21
• Division (/):
Divides the numerator by the denominator.
Note: When both operands are integers, the result is an integer (fractional part truncated).
int quotient = 10 / 3; // quotient is 3 (not 3.33)
double preciseQuotient = 10.0 / 3; // 3.333...
• Modulus (%):
Returns the remainder of division.
• int remainder = 10 % 3; // remainder is 1

2. Relational Operators

Relational operators compare two values and return a boolean (true or false). They are often used in
conditional statements.

• Equal to (==):
Checks if two values are equal.
boolean isEqual = (5 == 5); // true
• Not equal to (!=):
Checks if two values are not equal.
boolean isNotEqual = (5 != 3); // true
• Greater than (>):
Checks if the left operand is greater than the right.
boolean greater = (10 > 7); // true
• Less than (<):
Checks if the left operand is less than the right.
boolean less = (4 < 8); // true
• Greater than or equal to (>=):
Checks if the left operand is greater than or equal to the right.
boolean greaterOrEqual = (5 >= 5); // true
• Less than or equal to (<=):
Checks if the left operand is less than or equal to the right.
boolean lessOrEqual = (3 <= 7); // true

3. Boolean Logical Operators

These operators work with boolean values and are used to combine or modify boolean expressions.

• Logical AND (&&):


Returns true only if both operands are true.
boolean result = (5 > 3) && (10 > 7); // true
• Logical OR (||):
Returns true if at least one operand is true.
boolean result = (5 < 3) || (10 > 7); // true
• Logical NOT (!):
Inverts the boolean value.
boolean result = !(5 > 3); // false, because 5 > 3 is true

4. The Assignment Operator

The assignment operator (=) is used to assign values to variables.

• Basic Assignment:
int a = 10;
• Combined Assignment Operators:
These include operators like +=, -=, *=, /=, and %= which combine an arithmetic operation with
assignment.
int a = 5;
a += 3; // Equivalent to: a = a + 3; now a is 8

5. The Ternary Operator (The ? : Operator)

The ternary operator provides a compact way to perform conditional evaluation. Its syntax is:
condition ? valueIfTrue : valueIfFalse;

• Example:
int x = 10;
String result = (x % 2 == 0) ? "Even" : "Odd";
// result is "Even" because x % 2 equals 0

This operator is especially useful for simple conditional assignments and expressions.
6. Operator Precedence

Operator precedence determines the order in which operators are evaluated in an expression when no
parentheses are used. Java defines a specific hierarchy for operators. For example:

1. Highest Precedence:
Parentheses ()
2. Unary Operators:
!, unary + and -
3. Multiplicative Operators:
*, /, %
4. Additive Operators:
+, -
5. Relational Operators:
<, >, <=, >=
6. Equality Operators:
==, !=
7. Logical AND:
&&
8. Logical OR:
||
9. Assignment Operators:
=, +=, etc.

• Example Without Parentheses:


int result = 2 + 3 * 4; // result is 14, not 20, because * has higher
precedence than +

Understanding operator precedence is crucial for predicting how complex expressions are evaluated.

7. Using Parentheses

Parentheses () can be used to explicitly specify the order in which parts of an expression should be
evaluated, overriding the default operator precedence.

• Example:
int result = (2 + 3) * 4; // result is 20, because (2+3) is evaluated first

Parentheses improve readability and ensure that expressions are evaluated in the intended order.

Control statements in Java are fundamental constructs that direct the flow of execution based on
conditions, repeated execution, or explicit jumps. They are broadly classified into three groups:

• Selection Statements: Allow your program to choose between alternatives.


• Iteration Statements: Enable your program to repeat actions.
• Jump Statements: Change the normal sequential flow by breaking out of or continuing
iterations, or by returning from a method.

Below is a detailed explanation of each category and its components.


1. Selection Statements

Selection statements let you choose which block of code to execute based on some condition.
a. The if Statement

The if statement evaluates a boolean expression and executes its block of code if the condition is
true.

• Basic if:
int temperature = 25;
if (temperature > 20) {
System.out.println("It's warm outside.");
}

if-else:
int temperature = 15;
if (temperature > 20) {
System.out.println("It's warm outside.");
} else {
System.out.println("It's cool outside.");
}

if-else if-else:
int temperature = 20;
if (temperature > 30) {
System.out.println("It's hot outside.");
} else if (temperature >= 20) {
System.out.println("The weather is pleasant.");
} else {
System.out.println("It's cold outside.");
}

b. The Traditional switch Statement

The switch statement provides a way to select one of many code blocks to execute based on the value
of an expression (typically an int, char, String [since Java 7], or an enum).

• Syntax Example:
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
default:
System.out.println("Another day");
}

• Key Points:
o Case labels: Each case corresponds to a potential value.
o break Statement: Ends the current case to prevent fall-through.
o default: Executes if no case matches.
2. Iteration Statements

Iteration statements repeat a block of code multiple times. Java provides several types:
a. The while Loop

The while loop repeatedly executes its block of code as long as a specified condition remains true.

• Example:
int count = 0;
while (count < 5) {
System.out.println("Count is: " + count);
count++;
}

b. The do-while Loop

The do-while loop is similar to while but guarantees that the block is executed at least once because
the condition is checked after the loop body.

• Example:
int count = 0;
do {
System.out.println("Count is: " + count);
count++;
} while (count < 5);

c. The for Loop

The traditional for loop is ideal when the number of iterations is known beforehand. It consists of
three parts: initialization, condition, and update.

• Example:
for (int i = 0; i < 5; i++) {
System.out.println("Iteration: " + i);
}

d. The For-Each Version of the for Loop

Introduced in Java 5, the enhanced for loop simplifies iterating over arrays and collections by
automatically traversing each element.

• Example (Array):
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
System.out.println("Number: " + number);
}

• Example (Collection):
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : names) {
System.out.println("Name: " + name);
}

e. Local Variable Type Inference in a for Loop

Since Java 10, you can use the var keyword for local variable type inference, even in a loop header.
This reduces verbosity while still ensuring type safety.
• Example:
var numbers = new int[]{10, 20, 30, 40, 50};
for (var num : numbers) { // 'var' infers 'int' in this context.
System.out.println("Number: " + num);
}

f. Nested Loops

Nested loops occur when a loop resides inside the body of another loop. They are useful for multi-
dimensional structures like matrices.

• Example:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.print("(" + i + "," + j + ") ");
}
System.out.println(); // Move to the next line after inner loop
}

Keep in mind that nested loops can increase time complexity significantly.

3. Jump Statements

Jump statements change the normal flow of control within loops, switch statements, or methods.
a. Using break

• Purpose:
Terminates the nearest enclosing loop or switch statement immediately.
• Example in a Loop:
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // Exit the loop when i equals 5
}
System.out.println("i = " + i);
}
// Only prints i = 0 to 4.

• Example in a switch:
int value = 2;
switch (value) {
case 1:
System.out.println("One");
break;
case 2:
System.out.println("Two");
break; // Prevents fall-through.
default:
System.out.println("Other");
}

b. Using continue

• Purpose:
Skips the remaining statements in the current loop iteration and moves directly to the next
iteration.
• Example:
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
System.out.println("Odd number: " + i);
}
// Prints only odd numbers from 0 to 9.

c. Using return

• Purpose:
Exits from the current method and optionally returns a value to the caller.
• Example in a Method with a Return Value:
public int findMax(int a, int b) {
if (a > b) {
return a; // Exit method immediately returning a
} else {
return b; // Exit method immediately returning b
}
}

• Example in a void Method:


public void checkValue(int x) {
if (x < 0) {
System.out.println("Negative value");
return; // Exit method early
}
System.out.println("Non-negative value");
}
Module – 2
1. Introducing Classes
Class Fundamentals

• Definition:
A class is a blueprint or template that defines the state (fields) and behavior (methods)
common to a set of objects. Think of it as a custom data type.
• Structure:
A typical class declaration includes:
o Fields (instance variables) that hold data.
o Methods that perform operations on the data.
o Constructors that initialize new objects.
o Optionally, static members, inner classes, and access modifiers.
public class Person {
// Fields (state)
private String name;
private int age;

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

// Methods (behavior)
public void introduce() {
System.out.println("Hi, my name is " + name + " and I am " + age + "
years old.");
}
}

Declaring Objects

• Creating an Object:
Once a class is defined, you can create objects (instances) of that class using the new keyword.
• Person p = new Person("Alice", 30);
• Object Reference Variables:
Variables like p are not the object itself but references (pointers) to the object in memory.

Assigning Object Reference Variables

• Reference Assignment:
When you assign one object reference to another, both refer to the same object.
Person person1 = new Person("Bob", 25);
Person person2 = person1; // Both refer to the same Person instance

Introducing Methods

• Definition:
Methods define the behavior of a class. They can take parameters, perform actions, and
optionally return a value.
public void greet(String greeting) {
System.out.println(greeting + ", " + name);
}
Constructors

• Purpose:
Constructors are special methods used to initialize new objects. They have the same name as
the class and no return type.
• Overloading Constructors:
You can define multiple constructors with different parameters to allow various ways of object
initialization.
public Person() {
this.name = "Unknown";
this.age = 0;
}

The this Keyword

• Usage:
this refers to the current object instance. It’s commonly used to resolve naming conflicts (e.g.,
when a constructor parameter has the same name as a field) and to pass the current object to
other methods.
public Person(String name, int age) {
this.name = name; // 'this.name' is the field; 'name' is the parameter.
this.age = age;
}

Garbage Collection

• Automatic Memory Management:


Java uses garbage collection to automatically free memory used by objects that are no
longer reachable (i.e., there are no references pointing to them).
• How It Works:
The Java Virtual Machine (JVM) periodically scans for objects that are unreachable and
reclaims their memory.
• Finalization:
Although you can override the finalize() method for cleanup, its use is discouraged in
favor of explicit resource management (e.g., try-with-resources).

2. Methods and Classes (Advanced Topics)


Overloading Methods

• Definition:
Method overloading means having multiple methods in the same class with the same name but
different parameter lists (different number or types of parameters).
• Example:
public class Calculator {
public int add(int a, int b) {
return a + b;
}

public double add(double a, double b) {


return a + b;
}

public int add(int a, int b, int c) {


return a + b + c;
}
}

• Benefit:
Overloading provides flexibility and improves readability by allowing the same logical
operation (e.g., addition) for different types or numbers of inputs.
Objects as Parameters

• Passing Objects:
Methods can receive objects as parameters. In Java, object references are passed by value—
meaning the reference is copied, but both the original and the copy point to the same object.
public void updatePerson(Person p) {
p.setAge(40);
}

• Implication:
Changes to the object within the method affect the same object referenced outside the method.
Argument Passing

• Primitive vs. Object References:


o Primitive types: Passed by value, meaning the actual value is copied.
o Objects: The reference is passed by value; however, the object itself is not copied.
Therefore, methods can modify the object’s internal state.

Returning Objects

• Method Return Types:


Methods can return object references. This is useful for factory methods or methods that
compute and return a new object.
public Person clonePerson() {
return new Person(this.name, this.age);
}

Recursion

• Definition:
Recursion is a technique where a method calls itself to solve a problem by breaking it down
into smaller, similar subproblems.
• Example:
public int factorial(int n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}

Access Control

• Access Modifiers:
Java provides modifiers (public, protected, private, and package-private/default) to
control access to classes, fields, and methods.
o public: Accessible from any other class.
o protected: Accessible within the same package or subclasses.
o private: Accessible only within the defining class.
o Default (no modifier): Accessible only within the same package.
• Purpose:
Access control enforces encapsulation by hiding implementation details and exposing
only necessary parts of a class.

Understanding static

• Static Members:
static denotes that a field or method belongs to the class rather than any particular instance.
• Usage:
o Static Variables: Shared among all instances.
o Static Methods: Can be called without creating an object.
public class MathUtils {
public static double PI = 3.14159;

public static double square(double x) {


return x * x;
}
}

// Usage:
double area = MathUtils.PI * MathUtils.square(5);

• Static Context:
Inside a static method, you cannot refer to instance variables/methods directly.
Introducing final

• Final Variables:
When declared with final, a variable’s value cannot be changed after initialization. They act
as constants.
final int MAX_VALUE = 100;
• Final Methods:
A final method cannot be overridden in a subclass.
• Final Classes:
A final class cannot be subclassed.
public final class ImmutableData {
// Class cannot be extended.
}

Introducing Nested and Inner Classes

• Nested Classes:
A class declared within another class. There are two types:
o Static Nested Classes:
Behave like top-level classes but are logically grouped within an outer class. They
cannot access instance members of the outer class directly.
public class Outer {
static class StaticNested {
// Static nested class content
}
}

Inner Classes (Non-static Nested Classes):


Associated with an instance of the outer class and can access its members.
public class Outer {
private int outerValue = 10;

class Inner {
public void display() {
System.out.println("Outer value is " + outerValue);
}
}
}

// Creating an inner class instance:


Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();

• Local and Anonymous Classes:


o Local Classes: Declared within a block or method.
o Anonymous Classes: Defined and instantiated in a single expression; often used
to implement interfaces or extend classes for short-term use.
Module – 3

Inheritance in Java
Inheritance is one of the key mechanisms in object-oriented programming that allows a new class (the
subclass) to reuse, extend, or modify the behavior defined in an existing class (the superclass). This
mechanism promotes code reuse, logical organization, and polymorphism.

1. Inheritance Basics

• Concept:
Inheritance lets you create a new class based on an existing class. The new class automatically
acquires all (or most) of the fields and methods of the parent class. It can add its own members
or override inherited ones.
• Example:
public class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}

public class Dog extends Animal {


public void bark() {
System.out.println("Dog barks.");
}
}

Here, Dog inherits the eat() method from Animal and also adds its own method, bark().

2. Using super

• Purpose:
The keyword super is used within a subclass to refer explicitly to members (methods or fields)
of its superclass. It is also used to call the superclass constructor.
• Examples:
o Accessing Methods/Fields:
public class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}

public class Dog extends Animal {


public void eat() {
System.out.println("Dog is eating.");
}

public void callSuperEat() {


super.eat(); // Calls Animal's eat() method
}
}

o Calling Superclass Constructor:


public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}

public class Dog extends Animal {


public Dog(String name) {
super(name); // Invokes the Animal(String) constructor
}
}

3. Creating a Multilevel Hierarchy

• Definition:
Inheritance can span multiple levels. For example, a subclass can itself be extended, forming a
chain (multilevel inheritance).
• Example:
public class Animal {
public void eat() {
System.out.println("Animal eats.");
}
}

public class Mammal extends Animal {


public void nurse() {
System.out.println("Mammal nurses its young.");
}
}

public class Dog extends Mammal {


public void bark() {
System.out.println("Dog barks.");
}
}

In this hierarchy, Dog inherits from Mammal, which in turn inherits from Animal. Therefore, a
Dog object has access to eat(), nurse(), and bark().

4. When Constructors Are Executed

• Order of Execution:
When creating an object of a subclass, Java always calls the superclass constructor first. This
ensures that the superclass part of the object is initialized before the subclass adds its own
fields.
• Example:
public class Animal {
public Animal() {
System.out.println("Animal constructor called.");
}
}

public class Dog extends Animal {


public Dog() {
System.out.println("Dog constructor called.");
}
}

// Creating a Dog object:


Dog d = new Dog();
// Output:
// Animal constructor called.
// Dog constructor called.

5. Method Overriding

• Definition:
Overriding allows a subclass to provide a specific implementation for a method that is already
defined in its superclass.
• Rules:
o The overriding method must have the same signature (name and parameter types)
as the method in the superclass.
o The return type must be the same (or a subtype) of the original.
o Access level cannot be more restrictive.
• Example:
public class Animal {
public void makeSound() {
System.out.println("Animal sound.");
}
}

public class Dog extends Animal {


@Override
public void makeSound() {
System.out.println("Bark!");
}
}

6. Dynamic Method Dispatch

• Concept:
Dynamic method dispatch (or runtime polymorphism) means that the method to be executed is
determined at runtime based on the actual object type, not the reference type.
• Example:
Animal myAnimal = new Dog();// Reference of type Animal but object is Dog
myAnimal.makeSound(); // Calls Dog's makeSound() due to dynamic
dispatch

Even though myAnimal is declared as Animal, the actual method invoked is the one in Dog
because the object is an instance of Dog.

7. Using Abstract Classes

• Definition:
An abstract class is a class that cannot be instantiated directly and may contain abstract
methods (methods without a body) that must be implemented by subclasses.
• Example:
public abstract class Animal {
public abstract void makeSound(); // Abstract method
}

public class Dog extends Animal {


@Override
public void makeSound() {
System.out.println("Bark!");
}
}

Abstract classes are used to define a common template for subclasses while leaving some
implementation details to them.

8. Using final with Inheritance

• Final Methods:
When a method is declared as final, it cannot be overridden by subclasses.
• Final Classes:
A class declared as final cannot be subclassed.
• Example:
public final class ImmutableData {
// No class can extend ImmutableData
}

public class Animal {


public final void sleep() {
System.out.println("Sleeping...");
}
}

public class Dog extends Animal {


// Cannot override sleep() here
}

Using final helps enforce design decisions and can improve performance by allowing certain
optimizations.

9. Local Variable Type Inference and Inheritance

• Local Variable Type Inference:


Introduced in Java 10 using the var keyword, local variable type inference allows the compiler
to infer the type of a local variable from its initializer.
• Inheritance Context:
When working within an inheritance hierarchy, you can use var in methods just like any other
variable. For example, if a method returns an object from a hierarchy, the inferred type might
be the specific subclass.
• Example:
var dog = new Dog(); // dog is inferred as Dog
Animal animal = dog; // dog is assigned to a variable of type Animal

This feature improves code conciseness without affecting how inheritance works, since the actual types
and polymorphic behavior remain unchanged.

10. The Object Class

• Role:
In Java, every class implicitly extends the java.lang.Object class if no other superclass is
specified. Object is the root of the class hierarchy.
• Key Methods:
o toString(): Returns a string representation of the object.
o equals(Object obj): Compares the object with another for equality.
o hashCode(): Returns a hash code value for the object.
o getClass(): Returns the runtime class of the object.
o clone(): Creates and returns a copy of the object (if supported).
o finalize(): Called by the garbage collector before the object is reclaimed
(deprecated in recent versions).
• Example:
public class Person {
private String name;
public Person(String name) { this.name = name; }

@Override
public String toString() {
return "Person[name=" + name + "]";
}
}

Every class inherits these methods, so they can be overridden to customize behavior.

Interfaces in Java
Interfaces in Java define a contract that classes can implement. They specify method signatures that
implementing classes must fulfill, but they do not provide state (fields) or concrete implementations
(except for default methods introduced later).

1. Interfaces

• Definition:
An interface is a reference type that can contain abstract methods (by default), static methods,
default methods, and constant declarations.
• Purpose:
They allow you to define what methods a class must implement, enabling multiple inheritance
of type. Classes can implement multiple interfaces, which helps to achieve abstraction and
polymorphism.
• Example:
public interface Drawable {
void draw();
}

public class Circle implements Drawable {


@Override
public void draw() {
System.out.println("Drawing a circle.");
}
}

2. Default Interface Methods

• Introduction:
Starting with Java 8, interfaces can provide a default implementation for methods. This allows
developers to add new methods to interfaces without breaking existing implementations.
• Syntax and Example:
public interface Drawable {
void draw();
default void resize(int size) {
System.out.println("Default resizing by " + size);
}
}

public class Rectangle implements Drawable {


@Override
public void draw() {
System.out.println("Drawing a rectangle.");
}
// Inherits the default resize() method from Drawable
}

Default methods help extend interfaces while maintaining backward compatibility.

3. Static Methods in an Interface

• Purpose:
Interfaces may contain static methods that belong to the interface itself rather than to any
instance.
• Example:
public interface MathOperations {
static int add(int a, int b) {
return a + b;
}
}

// Usage:
int sum = MathOperations.add(5, 7); // sum is 12

Static interface methods are not inherited by implementing classes but can be called directly on the
interface.

4. Private Interface Methods

• Introduction:
Introduced in Java 9, private methods in interfaces allow you to encapsulate common code that
supports default methods. They are not accessible by implementing classes.
• Example:
public interface Logger {
default void logInfo(String message) {
log("INFO", message);
}

default void logError(String message) {


log("ERROR", message);
}

// Private method to support default methods


private void log(String level, String message) {
System.out.println("[" + level + "] " + message);
}
}

Private interface methods help reduce code duplication among default methods while hiding the
internal helper logic from the implementing classes.
Module – 4
Below is a detailed explanation of Java’s packages and exceptions, including their concepts, usage,
and examples.

Part 1. Packages

Java packages are a way to organize related classes and interfaces into namespaces. They help manage
large codebases by grouping related functionality, avoiding naming conflicts, and controlling access.
1. What Are Packages?

• Definition:
A package is a namespace that organizes a set of related classes and interfaces. It is analogous
to a folder in a file system.
• Purpose:
o Organization: Groups related classes together.
o Namespace Management: Avoids name collisions by qualifying class names with
a package name.
o Access Control: Provides package-level access; members without an explicit
access modifier (default, or package-private) are accessible only within the same
package.
o Modularity: Simplifies maintenance and code reuse.
• Declaration:
At the very top of a Java source file, you can declare the package with the package keyword:
• package com.example.myapp;

This statement must be the first non-comment line in the file.

• Naming Conventions:
Package names are typically all lowercase and follow the reverse-domain name convention
(e.g., com.company.project).

2. Packages and Member Access

• Access Modifiers and Packages:


o public: Members declared public are accessible from any package.
o protected: Members declared protected are accessible within the same package
and in subclasses (even in different packages).
o Default (package-private): If no access modifier is specified, the member is
accessible only within its own package.
o private: Accessible only within the class where it is declared.
• Example:
// In package com.example.shapes
package com.example.shapes;

public class Circle {


public double radius; // Accessible everywhere
double area; // Default: accessible only within
com.example.shapes
private String secretInfo; // Accessible only inside Circle

public Circle(double r) {
this.radius = r;
this.area = Math.PI * r * r;
this.secretInfo = "Circle secret";
}
}

Here, Circle’s radius is public (accessible from any package), while area is package-private
and secretInfo is private.

3. Importing Packages

• Import Statement:
The import statement lets you use classes and interfaces from other packages without needing
to specify their full names.
• Single Type Import:
• import java.util.ArrayList;

Now you can simply write ArrayList instead of java.util.ArrayList.

• On-Demand Import:
Use the asterisk * to import all public types from a package.
• import java.util.*;

Note: This does not import sub-packages.

• Static Import:
Allows you to import static members (fields and methods) so you can use them without class
qualification.
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

// Now you can use PI and sqrt directly:


double area = PI * sqrt(16);

Packages are a key organizational tool that help maintain clear code structure and controlled access
among different parts of your application.

Part 2. Exceptions

Exceptions provide a mechanism for handling errors and other exceptional events in a controlled way.
Instead of crashing the program, exceptions allow you to catch and handle error conditions gracefully.
1. Exception-Handling Fundamentals

• Concept:
Exceptions are objects that describe an unusual or error condition that occurs during the
execution of a program. They disrupt the normal flow of execution.
• Mechanism:
Java provides a structured approach with try, catch, and finally blocks to catch exceptions
and define cleanup actions.
2. Exception Types

• Checked Exceptions:
Exceptions that must be either caught or declared in the method’s throws clause. They
represent recoverable conditions (e.g., IOException).
• Unchecked Exceptions:
These are runtime exceptions (RuntimeException and its subclasses) and errors. They are not
required to be declared or caught (e.g., NullPointerException,
IllegalArgumentException).
• Errors:
Represent serious issues that are not intended to be caught (e.g., OutOfMemoryError).

3. Uncaught Exceptions

• Definition:
When an exception is thrown but not caught by any catch clause, it is considered
uncaught. This propagates up the call stack.
• Outcome:
Ultimately, if not handled, the JVM terminates the program and prints a stack trace.

4. Using try and catch

• Syntax:
try {
// Code that might throw an exception
} catch (ExceptionType e) {
// Handling code for the specific exception
}

• Example:
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero.");
}

5. Multiple catch Clauses

• Definition:
You can have multiple catch blocks after a try to handle different exception types.
• Example:
try {
// Code that might throw exceptions
int[] numbers = new int[2];
numbers[5] = 100; // Might throw ArrayIndexOutOfBoundsException
} catch (ArithmeticException e) {
System.out.println("Arithmetic error occurred.");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index is out of bounds.");
}

• Multi-catch (Java 7+):


You can combine multiple exception types into one catch clause.
try {
// risky code
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
System.out.println("An arithmetic or array error occurred.");
}
6. Nested try Statements

• Definition:
You can place a try block inside another try block. This is useful when a part of the code
requires its own exception handling.
• Example:
try {
// Outer try block
try {
int num = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("Invalid number format.");
}
// More code that might throw exceptions
} catch (Exception e) {
System.out.println("General error.");
}

7. The throw Statement

• Definition:
throw is used to explicitly throw an exception from a method or block of code.
• Example:
public void checkAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("Age must be at least 18.");
}
System.out.println("Age accepted.");
}

8. The throws Clause

• Definition:
The throws keyword is used in a method signature to declare that the method might throw
certain exceptions, and that callers must handle or further declare them.
• Example:
public void readFile(String filename) throws IOException {
FileReader reader = new FileReader(filename);
// ...
}

9. The finally Block

• Definition:
A finally block is used after a try (and optionally catch) blocks to execute code regardless of
whether an exception was thrown or caught.
• Example:
try {
// Code that might throw an exception
} catch (Exception e) {
System.out.println("Exception caught.");
} finally {
System.out.println("This will always execute.");
}

The finally block is often used to close resources like files or database connections.
10. Java’s Built-In Exceptions

• Common Exceptions:
o NullPointerException
o ArrayIndexOutOfBoundsException
o ArithmeticException
o IOException
o FileNotFoundException (a subclass of IOException)
o ClassNotFoundException
o etc.

These exceptions are provided by the Java API and cover a wide range of error conditions.
11. Creating Your Own Exception Subclasses

• Custom Exceptions:
You can create your own exception classes by extending Exception (for checked exceptions)
or RuntimeException (for unchecked exceptions).
• Example:
public class InvalidUserInputException extends Exception {
public InvalidUserInputException(String message) {
super(message);
}
}

You can then throw and catch your custom exception like any other:
public void validateInput(String input) throws InvalidUserInputException {
if (input == null || input.isEmpty()) {
throw new InvalidUserInputException("Input cannot be null or
empty.");
}
}

12. Chained Exceptions

• Definition:
Chained exceptions allow you to wrap a caught exception in a new exception, preserving the
original cause. This is useful for debugging and error tracing.
• Example:
public void processFile(String filename) throws CustomException {
try {
// Code that might throw IOException
FileReader reader = new FileReader(filename);
} catch (IOException e) {
throw new CustomException("Failed to process file: " + filename, e);
}
}

Here, the CustomException stores the original IOException as its cause, which you can later
retrieve via the getCause() method.

Module – 5

Multithreaded Programming in Java


Multithreading enables a program to perform several tasks concurrently. Java’s built-in support for
threads allows you to develop responsive and high‐performance applications. Let’s break down the
key topics:
1. The Java Thread Model

• Concept:
The Java Thread Model is built around the idea that a Java program can run several threads
concurrently. Each thread is an independent path of execution within a program. The Java
Virtual Machine (JVM) manages threads, scheduling them according to priorities and available
system resources.
• Key Points:
o Every Java application has at least one thread—the main thread.
o Threads can run concurrently, allowing for parallel execution on multi-core
processors.
o The thread scheduler (provided by the JVM) controls how threads are executed.

2. The Main Thread

• Definition:
The main thread is the entry point of any Java application. It is created automatically when the
program starts and executes the main() method.
• Example:
public class MainThreadExample {
public static void main(String[] args) {
System.out.println("Hello from the main thread!");
}
}

When the program runs, the main() method runs on the main thread.

3. Creating a Thread

There are two primary ways to create a thread in Java:

• By Extending the Thread Class:


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

// Creating and starting the thread:


MyThread thread = new MyThread();
thread.start(); // Always call start(), not run(), to initiate a new
thread.

• By Implementing the Runnable Interface:


public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running via Runnable.");
}
}

// Creating and starting the thread:


Thread thread = new Thread(new MyRunnable());
thread.start();

Implementing Runnable is generally preferred because it allows the class to extend another class if
needed.
4. Creating Multiple Threads

You can create multiple threads by instantiating and starting several thread objects. For example:
public class MultiThreadDemo implements Runnable {
private int threadNumber;

public MultiThreadDemo(int threadNumber) {


this.threadNumber = threadNumber;
}

@Override
public void run() {
System.out.println("Thread " + threadNumber + " is running.");
}

public static void main(String[] args) {


for (int i = 1; i <= 5; i++) {
Thread t = new Thread(new MultiThreadDemo(i));
t.start();
}
}
}

This code creates and starts five threads concurrently.


5. Using isAlive() and join()

• isAlive():
Returns true if a thread is still executing.
Thread t = new Thread(() -> System.out.println("Task"));
t.start();
if (t.isAlive()) {
System.out.println("Thread is still running.");
}

• join():
Causes the calling thread to wait until the thread on which join() is called has finished
executing.
Thread t = new Thread(() -> {
// Simulate work
try { Thread.sleep(1000); } catch (InterruptedException e) { }
System.out.println("Finished work.");
});
t.start();

try {
t.join(); // Wait for t to finish
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread resumes after join.");

6. Thread Priorities

• Purpose:
Thread priorities are used as hints to the thread scheduler about the importance of threads.
Threads with higher priority are generally executed in preference to lower-priority threads.
• Usage:
Thread highPriority = new Thread(() -> System.out.println("High priority"));
Thread lowPriority = new Thread(() -> System.out.println("Low priority"));

highPriority.setPriority(Thread.MAX_PRIORITY);
lowPriority.setPriority(Thread.MIN_PRIORITY);

highPriority.start();
lowPriority.start();

Note that actual scheduling behavior can depend on the operating system.
7. Synchronization

• Concept:
Synchronization is used to control access to shared resources in a multithreaded environment.
It prevents race conditions when multiple threads attempt to modify the same data
concurrently.
• Example Using a Synchronized Method:
public class Counter {
private int count = 0;

public synchronized void increment() {


count++;
}

public int getCount() {


return count;
}
}

• Example Using a Synchronized Block:


public void addToList(List<String> list, String value) {
synchronized (list) {
list.add(value);
}
}

8. Interthread Communication

• Mechanism:
Threads can communicate with each other using wait(), notify(), and notifyAll(). These
methods must be called from synchronized blocks or methods.
• Example:
public class SharedResource {
private boolean available = false;

public synchronized void produce() throws InterruptedException {


while (available) {
wait();
}
System.out.println("Producing resource");
available = true;
notifyAll();
}

public synchronized void consume() throws InterruptedException {


while (!available) {
wait();
}
System.out.println("Consuming resource");
available = false;
notifyAll();
}
}

This pattern coordinates producer/consumer threads.


9. Suspending, Resuming, and Stopping Threads

• Deprecated Methods:
Methods such as suspend(), resume(), and stop() were originally provided to control thread
execution but are deprecated due to safety issues (e.g., deadlocks and inconsistent object
states).
• Alternative Approaches:
Use flags or interrupt signals to control thread execution.
public class ControlledThread implements Runnable {
private volatile boolean running = true;

public void run() {


while (running) {
// Thread’s work here
}
System.out.println("Thread stopped gracefully.");
}

public void stopThread() {


running = false;
}
}

Using interruption (Thread.interrupt()) and checking Thread.isInterrupted() is another


common pattern.
10. Obtaining a Thread’s State

• Thread States:
Java threads can be in several states, defined by the Thread.State enum:
o NEW: Thread created but not started.
o RUNNABLE: Thread executing in the JVM.
o BLOCKED: Waiting for a monitor lock.
o WAITING: Waiting indefinitely for another thread’s action.
o TIMED_WAITING: Waiting for a specified period.
o TERMINATED: Thread has completed execution.
• Usage:
Thread t = new Thread(() -> System.out.println("Running"));
System.out.println("State before start: " + t.getState()); // NEW
t.start();
System.out.println("State after start: " + t.getState()); // RUNNABLE or
other

The getState() method helps you debug and monitor thread behavior.

Enumerations, Type Wrappers, and Autoboxing


Java provides additional constructs to improve type safety and code readability.

1. Enumerations
a. Enumeration Fundamentals

• Definition:
An enumeration (enum) is a special data type that represents a fixed set of constant values.
• Purpose:
Enums make code more readable and less error-prone when a variable can only have one out of
a small set of possible values.
• Example:
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

b. The values() and valueOf() Methods

• values():
Automatically provided method that returns an array of all enum constants in the order they are
declared.
for (Day d : Day.values()) {
System.out.println(d);
}

• valueOf(String name):
Converts a String to the corresponding enum constant.
Day day = Day.valueOf("MONDAY"); // Returns Day.MONDAY

These methods are very useful for iterating over and converting between strings and enum constants.

2. Type Wrappers

Java provides wrapper classes for each of the primitive types. These classes allow primitives to be
used as objects and include useful utility methods.
a. Character and Boolean Wrappers

• Character:
Wraps the char primitive.
Character ch = 'A';
boolean isDigit = Character.isDigit(ch); // false

• Boolean:
Wraps the boolean primitive.
Boolean flag = true;

b. Numeric Type Wrappers

Each numeric primitive has a corresponding wrapper class:

• Byte for byte


• Short for short
• Integer for int
• Long for long
• Float for float
• Double for double
• Usage Example:
• Integer num = 42; // Autoboxing (see next section)
• int n = num.intValue(); // Unboxing (or use auto-unboxing)

These wrappers are essential when working with collections or generics, which require objects rather
than primitives.
3. Autoboxing and Unboxing
a. Autoboxing and Methods

• Autoboxing:
Automatically converts a primitive value to its corresponding wrapper class when an
object is required.
List<Integer> numbers = new ArrayList<>();
numbers.add(5); // 5 is automatically boxed to an Integer object.

b. Autoboxing/Unboxing Occurs in Expressions

• Autoboxing in Expressions:
When an operation involves both primitive types and their wrapper classes, autoboxing (or
unboxing) happens automatically.
• Integer a = 10;
• int b = a + 5; // 'a' is unboxed to an int, then added to 5.
• Unboxing:
Automatically converts an object of a wrapper class to its corresponding primitive.
• Integer num = 100;
• int value = num; // Implicit unboxing

c. Autoboxing/Unboxing with Boolean and Character Values

• Boolean Example:
Boolean flagObj = true; // Autoboxing of boolean
boolean flag = flagObj; // Unboxing

• Character Example:
Character letterObj = 'Z'; // Autoboxing of char
char letter = letterObj; // Unboxing

Autoboxing and unboxing simplify code by removing the need for explicit conversions between
primitives and their corresponding wrapper objects.
Lab Programs:
1. Develop a JAVA program to add TWO matrices of suitable order N (The value of N
should be read from command line arguments).
import java.util.Scanner;
class AddMatrix {
public static void main(String args[]){
int i, j;
Scanner in = new Scanner(System.in);
int row = Integer.parseInt(args[0]);
int col = Integer.parseInt(args[1]);
int[][] mat1 = new int[row][col],
mat2 = new int[row][col],
res = new int[row][col];
System.out.println("Enter the elements of matrix1:");
for(i = 0; i < row; i++){
for(j = 0; j < col; j++)
mat1[i][j] = in.nextInt();
System.out.println();
}
System.out.println("Enter the elements of matrix2:");
for(i = 0; i < row; i++){
for(j = 0; j < col; j++)
mat2[i][j] = in.nextInt();
System.out.println();
}
for(i = 0; i < row; i++)
for(j = 0; j < col; j++)
res[i][j] = mat1[i][j] + mat2[i][j];
System.out.println("Sum of matrices:");
for(i = 0; i < row; i++){
for(j = 0; j < col; j++)
System.out.print(res[i][j] + "\t");
System.out.println();
}
}
}

2. Develop a stack class to hold a maximum of 10 integers with suitable methods.


Develop a JAVA main method to illustrate Stack operations.
import java.util.*;
public class Stack {
int[] arr; int top, size = 10;
Stack() { arr = new int[size]; top = -1; }
void push(int num) {
if(top == size - 1)
System.out.println("Stack OVERFLOW");
else
arr[++top] = num;
}
int pop() {
if(top == -1) {
System.out.println("Stack UNDERFLOW");
return 0;
}
int n = arr[top];
top--;
return n;
}
void display() {
for(int i = 0; i <= top; i++)
System.out.println(arr[i]);
}
public static void main(String[] args) {
Stack s = new Stack();
Scanner sc = new Scanner(System.in);
int item, ch;
String yn;
do {
System.out.println("Enter your choice");
System.out.println("1. Push");
System.out.println("2. Pop");
System.out.println("3. Display");
System.out.println("4. Exit");
ch = sc.nextInt();
switch(ch) {
case 1: System.out.println("Enter element to be pushed:");
item = sc.nextInt();
s.push(item);
break;
case 2: item = s.pop();
System.out.println("Item popped = " + item);
break;
case 3: System.out.println("Contents of Stack:");
s.display();
break;
case 4: System.out.println("Thank you");
System.exit(0);
break;
default: System.out.println("Invalid choice"); break;
}
System.out.println("Would you like to continue? (y/Y)");
yn = sc.next();
} while(yn.equals("y") || yn.equals("Y"));
System.out.println("Thank you");
}
}

3. Develop a JAVA program to create a class named shape. Create three sub classes
namely: circle, triangle and square, each class has two member functions named
draw () and erase (). Demonstrate polymorphism concepts by developing suitable
methods, defining member data and main program.
class Shape {
protected String name;
Shape(String name){ this.name = name; }
void draw(){ System.out.println("Drawing a " + name); }
void erase(){ System.out.println("Erasing a " + name); }
}
class Circle extends Shape {
private double r;
Circle(String name, double r){ super(name); this.r = r; }
public void draw(){ System.out.println("Drawing a circle with radius "
+ r); }
public void erase(){ System.out.println("Erasing a circle with radius "
+ r); }
}
class Triangle extends Shape {
private double b, h;
Triangle(String name, double b, double h){ super(name); this.b = b;
this.h = h; }
public void draw(){ System.out.println("Drawing a triangle with base "
+ b + " and height " + h); }
public void erase(){ System.out.println("Erasing a triangle with base "
+ b + " and height " + h); }
}
class Square extends Shape {
private double s;
Square(String name, double s){ super(name); this.s = s; }
public void draw(){ System.out.println("Drawing a square with side
length " + s); }
public void erase(){ System.out.println("Erasing a square with side
length " + s); }
}
public class ShapeDemo {
public static void main(String[] args){
Shape[] shapes = {new Circle("Circle", 5.0), new
Triangle("Triangle", 4.0, 6.0), new Square("Square", 3.0)};
for(Shape shape : shapes){
shape.draw();
shape.erase();
System.out.println();
}
}
}

4. Develop a JAVA program to create an abstract class Shape with abstract methods
calculateArea() and calculatePerimeter(). Create subclasses Circle and Triangle that
extend the Shape class and implement the respective methods to calculate the area
and perimeter of each shape.
abstract class Shape {
abstract double calculateArea();
abstract double calculatePerimeter();
}
class Circle extends Shape {
double r;
Circle(double r){ this.r = r; }
double calculateArea(){ return Math.PI * r * r; }
double calculatePerimeter(){ return 2 * Math.PI * r; }
}
class Triangle extends Shape {
double ts1, ts2, ts3;
Triangle(double ts1, double ts2, double ts3){ this.ts1 = ts1; this.ts2
= ts2; this.ts3 = ts3; }
double calculateArea(){
double s = (ts1 + ts2 + ts3) / 2;
return Math.sqrt(s * (s - ts1) * (s - ts2) * (s - ts3));
}
double calculatePerimeter(){ return ts1 + ts2 + ts3; }
}
public class Main {
public static void main(String[] args) {
double r = 4.0, ts1 = 3.0, ts2 = 4.0, ts3 = 5.0;
Circle circle = new Circle(r);
Triangle triangle = new Triangle(ts1, ts2, ts3);
System.out.println("Radius of the Circle" + r);
System.out.println("Area of the Circle: " +
circle.calculateArea());
System.out.println("Perimeter of the Circle: " +
circle.calculatePerimeter());
System.out.println("\nSides of the Traiangel are: " + ts1 + "," +
ts2 + "," + ts3);
System.out.println("Area of the Triangle: " +
triangle.calculateArea());
System.out.println("Perimeter of the Triangle: " +
triangle.calculatePerimeter());
}
}

5. Develop a JAVA program to create an interface Resizable with methods


resizeWidth(int width) and resizeHeight(int height) that allow an object to be resized.
Create a class Rectangle that implements the Resizable interface and implements the
resize methods.
interface Resizable {
void resizeWidth(int width);
void resizeHeight(int height);
}
class Rectangle implements Resizable {
private int width, height;
public Rectangle(int width, int height) { this.width = width;
this.height = height; }
public void resizeWidth(int width) { this.width = width; }
public void resizeHeight(int height) { this.height = height; }
public void printSize() { System.out.println("Width: " + width + ",
Height: " + height); }
}
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(100, 150);
rectangle.printSize();
rectangle.resizeWidth(150);
rectangle.resizeHeight(200);
rectangle.printSize();
}
}

6. Develop a JAVA program to raise a custom exception (user defined exception) for
DivisionByZero using try, catch, throw and finally.
class MyException extends Exception {
private int ex;
MyException(int b) { ex = b; }
public String toString() { return "My Exception : Number is not divided
by " + ex; }
}
class DivideByZeroException {
static void divide(int a, int b) throws MyException {
if(b <= 0)
throw new MyException(b);
System.out.println("Division : " + a / b);
}
public static void main(String[] args) {
try {
divide(10, 0);
} catch(MyException me) {
System.out.println(me);
} finally { }
}
}

7. Write a program to illustrate creation of threads using runnable class. (start method
start each of the newly created thread. Inside the run method there is sleep() for
suspend the thread for 500 milliseconds).
class MyRunnable implements Runnable {
private volatile boolean running = true;
public void run() {
while(running) {
try {
Thread.sleep(500);
System.out.println("Thread ID: " +
Thread.currentThread().getId() + " is running.");
} catch(InterruptedException e) {
System.out.println("Thread interrupted.");
}
}
}
public void stopThread(){ running = false; }
}
public class p7 {
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable(), r2 = new MyRunnable(), r3 = new
MyRunnable(), r4 = new MyRunnable(), r5 = new MyRunnable();
Thread t1 = new Thread(r1), t2 = new Thread(r2), t3 = new
Thread(r3), t4 = new Thread(r4), t5 = new Thread(r5);
t1.start(); t2.start(); t3.start(); t4.start(); t5.start();
try { Thread.sleep(3000); } catch(InterruptedException e){}
}
}

8. Develop a program to create a class MyThread in this class a constructor, call the
base class constructor, using super and start the thread. The run method of the class
starts after this. It can be observed that both main thread and created child thread are
executed concurrently.
class MyThread extends Thread {
public MyThread(String name) {
super(name);
start();
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " Count:
" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "
Thread interrupted.");
}
}
}
}
public class p8 {
public static void main(String[] args) {
new MyThread("Child Thread");
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " Thread
Count: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "
Thread interrupted.");
}
}
}
}

You might also like