Java_SubjectSummaryNotes
Java_SubjectSummaryNotes
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
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:
For example, an abstract class Animal may declare a method makeSound(), and different subclasses
like Dog and Cat provide their own implementations.
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.
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:
• 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.
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.
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
• 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:
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
• 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.
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
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:
• 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.
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
These operators work with boolean values and are used to combine or modify boolean expressions.
• 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
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.
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 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.");
}
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++;
}
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);
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);
}
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);
}
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
}
}
• 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.
• 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;
}
• 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
• 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;
}
• 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
Returning Objects
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;
// 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.
}
• 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
}
}
class Inner {
public void display() {
System.out.println("Outer value is " + outerValue);
}
}
}
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.");
}
}
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.");
}
}
• 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.");
}
}
In this hierarchy, Dog inherits from Mammal, which in turn inherits from Animal. Therefore, a
Dog object has access to eat(), nurse(), and bark().
• 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.");
}
}
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.");
}
}
• 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.
• 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
}
Abstract classes are used to define a common template for subclasses while leaving some
implementation details to them.
• 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
}
Using final helps enforce design decisions and can improve performance by allowing certain
optimizations.
This feature improves code conciseness without affecting how inheritance works, since the actual types
and polymorphic behavior remain unchanged.
• 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();
}
• 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);
}
}
• 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.
• 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);
}
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;
• Naming Conventions:
Package names are typically all lowercase and follow the reverse-domain name convention
(e.g., com.company.project).
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;
• On-Demand Import:
Use the asterisk * to import all public types from a package.
• import java.util.*;
• 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;
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.
• 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.");
}
• 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.");
}
• 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.");
}
• 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.");
}
• 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);
// ...
}
• 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.");
}
}
• 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
• 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.
• 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
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;
@Override
public void run() {
System.out.println("Thread " + threadNumber + " is running.");
}
• 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;
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;
• 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;
• 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.
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
}
• 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;
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.
• 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
• 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();
}
}
}
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());
}
}
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.");
}
}
}
}