Java
Java
ND 2 (COM 211)
DISTINCT POLYTEHNIC
ADEYANJU A.
INTRODUCTION TO JAVA
Java programs are built from several fundamental components that work together to execute
instructions. Think of it like building with LEGO bricks – each type of brick has a specific
purpose, and when you put them together correctly, you create something functional. Here are
the basic building blocks:
Classes: At its heart, Java is an object-oriented programming (OOP) language, and the
class is the blueprint for creating objects. A class defines the data (attributes or fields)
and the behavior (methods or functions) that objects of that class will have. Imagine a
blueprint for a car – it specifies the car's properties (color, number of doors) and what it
can do (start, accelerate, brake).
Objects: An object is an instance of a class. It's the actual "car" built from the blueprint.
You can have many objects of the same class, each with its own specific data values. For
example, you could have several car objects, each with a different color and mileage, all
based on the same car class blueprint.
Methods: Methods define the actions or operations that an object can perform. They
contain a sequence of statements that are executed to achieve a specific task. Think of the
methods of a car object: startEngine(), accelerate(), brake(). Each method performs a specific
action.
Variables: Variables are used to store data. In Java, you need to declare the type of data
a variable will hold (e.g., integer, text, boolean). Within a class, variables are called fields
or instance variables if they belong to an object, and static variables or class variables
if they belong to the class itself. Inside a method, variables are called local variables.
Imagine a car having a variable to store its current speed or its color.
Statements: Statements are individual instructions that the Java compiler executes.
They can be assignments (e.g., setting the value of a variable), conditional statements ( if-
else), loops (for, while), or method calls. Think of statements as the individual steps in a
recipe.
Packages: Packages are used to organize related classes and interfaces into namespaces.
This helps to prevent naming conflicts and makes it easier to manage large projects.
Imagine organizing your LEGO bricks into different boxes based on their type or color.
Interfaces: An interface is a blueprint of a contract that specifies a set of methods that a
class must implement. It defines what a class should do, but not how it should do it.
Think of it as a job description – it lists the required skills, but not the specific way you
should perform each task.
Comments: Comments are notes in the code that are ignored by the compiler. They are
used to explain the code and make it more readable for humans. Think of them as little
sticky notes you leave on your LEGO creation to remind yourself (or others) what a
certain part is for.
These components work together to form the structure and behavior of Java programs, allowing
developers to create a wide range of applications.
2
Data Types: Defining the Kind of Information
A data type specifies the kind of values that a variable can hold and the operations that can be
performed on it. It's like categorizing information so the computer knows how to store and
manipulate it correctly. Java has two main categories of data types:
1. Primitive Data Types: These are the most basic data types built into the Java language. They
directly hold a value. Here are some common ones:
byte: Stores small whole numbers (integers) ranging from -128 to 127. Think of it for
representing very small counts or flags.
short: Stores larger whole numbers than byte, ranging from -32,768 to 32,767.
int: Stores whole numbers ranging approximately from -2 billion to +2 billion. This is the
most commonly used data type for integers.
long: Stores very large whole numbers, beyond the range of int. Useful for representing
things like timestamps or large IDs. You typically append an L to a long literal (e.g.,
1234567890123L).
float: Stores single-precision floating-point numbers (numbers with decimal points). You
typically append an F to a float literal (e.g., 3.14F). Be mindful of potential precision issues
with floating-point numbers.
double: Stores double-precision floating-point numbers, providing more precision than
float. This is generally the preferred data type for decimal numbers.
boolean: Stores either true or false values. Useful for representing logical conditions.
char: Stores a single Unicode character, like a letter, digit, or symbol (e.g., 'A', '7', '$').
2. Reference Data Types: These data types do not directly store the value but instead hold a
reference (an address in memory) to the actual data, which is an object.
Classes: As we discussed earlier, classes are blueprints for objects. Variables of a class
type will hold a reference to an object of that class. For example, if you have a Car class, a
variable of type Car will refer to a specific Car object.
Interfaces: Similar to classes, interface variables can hold references to objects of any
class that implements that interface.
Arrays: Arrays are collections of elements of the same data type. An array variable holds
a reference to this collection in memory. For example, you can have an array of integers
(int[]) or an array of strings (String[]).
Strings: Although often treated as a primitive in other languages, in Java, String is a class.
String variables hold references to String objects, which represent sequences of characters.
Choosing the right data type is important because it affects how much memory is used and what
kind of operations can be performed on the data.
3
Variables: Named Storage Locations
A variable is a named storage location in the computer's memory that holds a value of a specific
data type. Think of it as a labeled box where you can put information.
Declaration: Before you can use a variable, you need to declare it. This involves specifying the
variable's data type and its name. For example:
Declaring a variable in Java means specifying its data type and giving it a name (identifier). This
reserves a space in the computer's memory to hold a value of that specific type and associates
that memory location with the chosen name
The basic syntax for declaring a variable is:
dataType variableName;
dataType: This specifies the type of data the variable will hold. It can be any of the primitive
data types (like int, double, boolean, char, byte, short, long, float) or a reference type (like String,
or the name of a class you've created).
variableName: This is the identifier you'll use to refer to the variable in your code. It must
follow Java's naming rules (e.g., cannot start with a digit, cannot be a reserved keyword, is case-
sensitive). Choose descriptive names that indicate the variable's purpose.
You can also declare multiple variables of the same data type in a single statement by separating
their names with commas:
String firstName, lastName; // Declares two String variables: firstName and lastName
4
Initialization During Declaration:
You can also assign an initial value to a variable at the time of its declaration:
String message = "Hello!"; // Declares a String variable 'message' and initializes it to "Hello!"
boolean isActive = true; // Declares a boolean variable 'isActive' and initializes it to true
Initialization: Constants must be initialized when they are declared. You cannot declare a final
variable without assigning it a value immediately (unless it's a blank final variable, which is a
more advanced concept).
Naming Convention: Using uppercase with underscores for constant names improves code
readability and makes it clear that these are values that should not be modified.
5
Preventing Errors: The final keyword prevents accidental modification of values that should
remain constant, leading to more robust code.
In summary, variable declaration in Java sets aside storage for data that can change, while
constant declaration, using the final keyword, defines storage for data whose value remains fixed
throughout the program's execution. Both are essential for managing data within your Java
applications.
First, you'll use a text editor or an IDE to write your Java code and save it in a file named
Greeting.java.
System.out.println(message);
In this code:
We define a public class named Greeting. Remember, the filename must match the public
class name (Greeting.java).
Inside the class, we have the main method, which is the entry point of our program.
We declare a String variable named message and assign it the value "Hello from Osogbo,
Nigeria!".
We use System.out.println() to print the value of the message variable and a string containing
the current date.
6
macOS: Open the "Terminal" application (you can find it in Applications > Utilities).
Navigate to the Directory: Use the cd command to navigate to the folder where you saved
the Greeting.java file. For example, if you saved it in a folder named "JavaProjects" on your
Desktop, you might use commands like:
Windows:
Bash
cd Desktop\JavaProjects
macOS/Linux:
Bash
cd Desktop/JavaProjects
Run the Java Compiler (javac): Once you are in the correct directory, execute the following
command:
Bash
javac Greeting.java
Stay in the Same Command Prompt or Terminal: Make sure you are still in the directory
where the Greeting.class file is located.
Run the Java Virtual Machine (java): Execute the following command:
Bash
7
java Greeting
Important: Notice that you use java followed by the name of the class that contains the main
method (Greeting), without the .class extension.
Observe the Output: The Java Virtual Machine (JVM) will load the bytecode from
Greeting.class and execute the main method. You should see the following output printed to
your console:
Write Code (.java file): You write your Java source code in a file (e.g., Greeting.java).
Compile (javac): You use the javac command to translate your .java file into .class bytecode
(e.g., Greeting.class).
Run (java): You use the java command followed by the class name (without .class) to execute
the bytecode on the JVM.
This two-step process of compilation and execution is what allows Java programs to be
platform-independent. The .class file can be run on any operating system that has a compatible
JVM installed.
8
JAVA CONSTRUCTS AND THEIR APPLICATIONS
Java constructs are the fundamental building blocks of the Java programming language. They
include keywords, operators, control flow statements, classes, objects, methods, and
constructors. These constructs enable developers to create a wide range of applications, from
simple command-line tools to complex enterprise-level systems.
Data Types: Java has primitive data types (e.g., int, float, boolean, char) for storing basic values
and reference types (e.g., String, arrays, classes) for storing objects.
Variables: Variables are used to store data. They have a name, a type, and a value that can be
changed during program execution.
Operators: Operators perform operations on variables and values. These include arithmetic
operators (+, -, *, /, %), relational operators (==, !=, >, <, >=, <=), logical operators (&&, ||, !),
and assignment operators (=, +=, -=, etc.).
Control Flow Statements: These statements control the order in which code is executed.
Examples include:
Classes are blueprints for creating objects. They define the properties (data) and behaviors
(methods) that objects of that class will have.
Objects are instances of classes. They are the actual entities that interact within a Java program.
Methods: Methods are blocks of code that perform specific tasks. They can take input parameters
and return a value.
Constructors: Constructors are special methods used to initialize objects when they are created.
They have the same name as the class and do not have a return type.
9
Advanced Java Constructs
Beyond the basics, Java offers more advanced constructs for building sophisticated applications:
Interfaces: Interfaces define a contract that classes can implement. They specify methods that
implementing classes must provide.
Abstract Classes: Abstract classes cannot be instantiated and may contain abstract methods
(methods without a body). They serve as base classes for other classes.
Inheritance: Inheritance allows a class (subclass or derived class) to inherit properties and
methods from another class (superclass or base class), promoting code reuse. The extends
keyword is used for inheritance. The super() keyword is used in a subclass constructor to call the
constructor of the superclass and to access superclass methods.
Exception Handling: Java provides mechanisms (try, catch, finally, throw, throws) to handle
runtime errors (exceptions) gracefully, preventing program termination.
Generics: Generics provide type safety to collections and other parameterized types, allowing
you to specify the type of objects that can be stored.
Multithreading: Java supports the creation and management of multiple threads of execution
within a single program, enabling concurrency and improving performance for tasks that can be
done in parallel.
Annotations: Annotations provide metadata about the code. They can be used by the compiler or
runtime environment for various purposes, such as code generation or configuration.
Reflection: Reflection allows a program to inspect and manipulate its own structure (classes,
methods, fields) at runtime.
Streams API: Also introduced in Java 8, the Streams API provides a powerful way to process
collections of data using functional-style operations.
10
Applications of Java
Due to its robustness, platform independence, security features, and extensive libraries, Java is
used in a vast array of applications across various domains:
Mobile Applications: Java is the primary language for developing Android mobile applications.
Web Applications: Java is widely used for building large-scale web applications using
technologies like Servlets, JavaServer Pages (JSP), and frameworks such as Spring and Jakarta
EE (formerly Java EE).
Enterprise Applications: Java is a popular choice for developing complex, scalable, and secure
enterprise-level applications for finance, banking, supply chain management, and more.
Desktop GUI Applications: Java can be used to create desktop applications with graphical user
interfaces using frameworks like Swing, JavaFX, and AWT (Abstract Window Toolkit).
Gaming Applications: Java is used in game development, particularly for mobile games and with
game engines like jMonkeyEngine for 2D and 3D games.
Big Data Technologies: Java is a core component in many big data processing frameworks like
Hadoop and Spark.
Cloud Computing: Java is heavily utilized in cloud-based applications and services, with
platforms like Oracle Java Cloud Service.
Scientific Applications: Java is used for scientific and mathematical computations due to its
performance and libraries. Examples include applications like MATLAB.
Financial Applications: The finance industry relies heavily on Java for developing secure and
high-performance trading platforms, settlement systems, and risk management tools.
Embedded Systems: Java ME (Micro Edition) was designed for embedded systems and mobile
devices, although Android has become more dominant in the mobile space. Java is still used in
some embedded systems like Blu-ray players and SIM cards.
Internet of Things (IoT): Java's portability and scalability make it a viable option for developing
applications for IoT devices.
Software Development Tools: Many popular Integrated Development Environments (IDEs) like
IntelliJ IDEA, Eclipse, and NetBeans are developed in Java.
11
Web Servers and Application Servers: The Java ecosystem includes numerous robust web
servers (e.g., Apache Tomcat) and application servers (e.g., GlassFish, WildFly) written in Java.
Object Declaration:
What it is: Object declaration is the process of defining a variable of a specific class type. It tells
the compiler that you intend to hold a reference to an object of that class with this variable name.
What happens:
o It allocates space in memory for the reference variable itself. This variable will eventually hold
the memory address of an actual object.
o At this stage, no actual object of the class is created in the heap memory.
o The reference variable is typically initialized to null, meaning it doesn't currently point to any
object.
Syntax:
ClassName objectName;
o ClassName: The name of the class whose object you intend to reference.
o objectName: The name you choose for your reference variable.
Analogy: Think of it like writing down the address of a house on a piece of paper. You have the
address (the reference variable), but the actual house (the object) doesn't exist yet at that address.
Object Creation:
What it is: Object creation is the process of actually instantiating an object of a class in memory.
This is done using the new keyword followed by a call to the class's constructor.
What happens:
o The new keyword allocates memory on the heap for a new object of the specified class.
o The constructor of the class is called to initialize the object's state (its instance variables).
o The new operator returns the memory address of the newly created object.
o This memory address is then assigned to the reference variable that was previously declared.
Syntax:
Java
12
Example:
Java
public class Dog {
String name;
// Object Creation
myDog = new Dog("Buddy"); // A new Dog object is created in the heap,
// and its memory address is assigned to 'myDog'.
// Now you can interact with the object through the reference variable
myDog.bark(); // Calls the bark() method of the Dog object that 'myDog' refers to.
System.out.println(myDog.name); // Accesses the 'name' instance variable.
In essence:
You must create an object before you can use its methods or access its instance variables (unless
they are static). The reference variable acts as a pointer or handle to the actual object in memory.
In Java, a class serves as a blueprint or template for creating objects. An instantiable class is
simply a class from which you can create objects (instances). This is the standard and most
common type of class you'll encounter in Java.
They have a constructor: Every class has at least one constructor. If you don't explicitly define
one, Java provides a default no-argument constructor. Constructors are special methods used to
initialize the state of an object when it's created using the new keyword.
You can use the new keyword: The fundamental operation for creating an object of an
instantiable class is using the new keyword followed by a call to one of the class's constructors.
13
They define the blueprint: They specify the data (instance variables or fields) that objects of
the class will hold and the behavior (methods) that these objects can perform.
The process of creating an object from a class is called instantiation. When you instantiate a
class, you are essentially asking the Java Virtual Machine (JVM) to:
1. Allocate memory: Allocate space in the heap memory to hold the object's data.
2. Initialize the object: Call the appropriate constructor of the class to set the initial values of the
object's instance variables.
3. Return a reference: Return a reference (the memory address) to the newly created object. This
reference is typically stored in a variable of the class type.
Java
public class Car { // This is an instantiable class
String model;
String color;
// Constructor
public Car(String model, String color) {
this.model = model;
this.color = color;
}
// Method
public void displayDetails() {
System.out.println("Model: " + model + ", Color: " + color);
}
In this example:
14
myCar and yourCar are object references that hold the memory addresses of the created Car
objects.
While most classes are instantiable, there are cases where you might design a class that is not
meant to be instantiated directly:
1. Abstract Classes:
o Declared using the abstract keyword.
o May contain abstract methods (methods without implementation).
o Cannot be instantiated directly using new.
o Their purpose is to serve as a blueprint for subclasses, which can be instantiated (if they
implement all abstract methods).
2. Utility Classes:
o Often contain only static methods and constants.
o Designed to provide a collection of utility functions that operate on data passed to them, rather
than having an internal state.
o To prevent instantiation, they typically have a private constructor.
What they are: Fields, also known as instance variables or member variables, are variables
declared directly within a class but outside of any method. They represent the data or state of an
object of that class. Each object of the class will have its own copy of these instance variables.
Purpose: Fields store the attributes or characteristics that define an object. For example, a Car
class might have fields for model, color, speed, and fuelLevel.
Declaration: Fields are declared using the following syntax:
o access_modifier (optional): Controls the visibility and accessibility of the field from other parts of
the program. Common access modifiers include public, private, protected, and (default) package-
private.
o static (optional): If a field is declared static, it belongs to the class itself, not to any specific
object. There's only one copy of a static field shared among all instances of the class.
o final (optional): If a field is declared final, its value can only be assigned once, either during
declaration or within the constructor. Once assigned, its value cannot be changed. These are
often used for constants.
o data_type: Specifies the type of data the field will hold (e.g., int, String, boolean, or a reference to
another object).
o fieldName: The name you choose for the field. Follow Java's naming conventions (camelCase).
15
o (optional): You can assign an initial value to the field when it's declared. If you
= initialValue
don't, Java assigns a default value (e.g., 0 for numeric types, false for boolean, null for object
references).
Example:
Java
dog1.displayInfo(); // Output: Name: Buddy, Age: 3, Breed: Golden Retriever, Owner: Alice
dog2.displayInfo(); // Output: Name: Lucy, Age: 5, Breed: Golden Retriever, Owner: Bob
Java Constructors
What they are: Constructors are special methods within a class that are used to initialize
the objects of that class when they are created using the new keyword.
Purpose: Constructors ensure that an object is in a valid and usable state when it's first
created. They typically assign initial values to the object's fields.
Characteristics:
o Name: A constructor has the same name as the class.
o No Return Type: Constructors do not have a return type, not even void.
16
o Invocation: Constructors are automatically called when you use the new keyword
to create an object.
o Multiple Constructors (Overloading): A class can have multiple constructors
with different parameter lists. This is called constructor overloading and allows
you to create objects with different initial configurations.
o Default Constructor: If you don't explicitly define any constructors in your
class, Java provides a default no-argument constructor (a constructor with no
parameters). However, if you define at least one constructor, the default
constructor is no longer automatically provided.
Syntax:
Java
[access_modifier] ClassName([parameterList]) {
// Initialization code for the object's fields
}
17
// Constructor 2: Takes only name and owner (age defaults to 0)
public Dog(String name, String owner) {
this(name, 0, owner); // Calling the first constructor using 'this()'
System.out.println("Second constructor called for " + name);
}
Constructors are the primary mechanism for initializing the fields (instance variables) of an
object when it's created. The parameters passed to a constructor often correspond to the initial
values you want to assign to the object's fields.
In summary, fields define what an object knows (its data), and constructors define how an
object is initially set up when it's created. They are essential for creating well-behaved and
predictable objects in Java.
Alright, let's explore methods, method overloading, garbage collection, and nested classes in
Java. These are important concepts for writing organized, efficient, and powerful Java code.
Methods
What they are: Methods are blocks of code within a class that perform specific tasks.
They define the behavior of the objects of that class. You interact with an object by
calling its methods.
Purpose:
o Encapsulate a sequence of statements to perform a specific operation.
o Promote code reusability by allowing you to call the same block of code multiple
times.
o Make code more organized and easier to understand by breaking down complex
tasks into smaller, manageable units.
Declaration: Methods are declared using the following syntax:
[access_modifier] [static] [final] [abstract] [native] [synchronized] return_type
methodName([parameterList]) [throws exceptionList]
{ // Method body - statements to be executed}
18
Let's break down the components:
o access_modifier: Controls the visibility and accessibility of the method (e.g., public,
private, protected, default).
o static: If a method is static, it belongs to the class itself, not to any specific object.
You can call it directly using the class name (e.g., Math.sqrt(25)). Static methods
cannot directly access instance variables or non-static methods of the class
without creating an object.
o final: If a method is final, it cannot be overridden by subclasses.
o abstract: If a method is abstract, it has no implementation in the current class. The
class containing an abstract method must also be declared abstract. Subclasses must
provide implementations for abstract methods.
o native: Indicates that the method's implementation is written in another
programming language (like C or C++) using the Java Native Interface (JNI).
o synchronized: Used in multithreaded environments to control access to a method,
ensuring that only one thread can execute it at a time.
o return_type: Specifies the data type of the value that the method returns. If the
method doesn't return any value, the return type is void.
o methodName: The name you choose for the method. Follow Java's naming
conventions (camelCase).
o parameterList (optional): A comma-separated list of parameters that the method
accepts. Each parameter has a data type and a name (e.g., int age, String name).
These are the inputs to the method.
o throws exceptionList (optional): Specifies the exceptions that the method might
throw.
o { ... }: The method body contains the actual code that will be executed when the
method is called.
Calling Methods: You call a method using the dot operator (.) on an object reference
(for instance methods) or the class name (for static methods):
Java
Method Overloading
What it is: Method overloading allows you to define multiple methods within the same
class that have the same name but different parameter lists. The parameter lists must
differ in either the number of parameters, the data types of the parameters, or the order of
the data types of the parameters.
Purpose:
19
oProvides flexibility by allowing you to call a method with different types or
numbers of arguments, making the class more user-friendly.
o Enhances code readability by using the same logical name for operations that are
conceptually similar but operate on different data.
How it Works: The Java compiler determines which overloaded method to call based on
the arguments passed during the method invocation. This is known as compile-time
polymorphism or static binding.
Example:
Java
Garbage Collection
What it is: Garbage collection is an automatic memory management process in Java. The
Java Virtual Machine (JVM) automatically reclaims memory that is no longer being used
by the program.
Purpose:
o Frees up memory occupied by objects that are no longer reachable (i.e., no longer
have any active references pointing to them).
20
o Prevents memory leaks, which can occur in languages with manual memory
management (like C++) where programmers have to explicitly deallocate
memory.
o Simplifies memory management for developers, allowing them to focus on the
application logic rather than manual memory deallocation.
How it Works (Simplified):
o The JVM has a garbage collector that runs in the background.
o The garbage collector identifies objects that are no longer reachable from the
"root" objects (e.g., local variables on the stack, static variables).
o Once unreachable objects are identified, the memory they occupy is marked for
reclamation.
o The garbage collector then performs actions like:
Deleting: Simply removing the unreachable objects from memory.
Compacting: Moving the remaining reachable objects together to reduce
fragmentation of the heap memory.
o The specific algorithms and strategies used by the garbage collector can vary
depending on the JVM implementation.
Important Points:
o You cannot explicitly force garbage collection in a guaranteed way in Java.
Calling System.gc() or Runtime.getRuntime().gc() suggests to the JVM that it might be
a good time to run the garbage collector, but the JVM is free to ignore this
suggestion.
o Garbage collection happens automatically in the background. You generally don't
need to worry about manually freeing memory.
o While automatic garbage collection simplifies memory management, it can
introduce occasional pauses in program execution (garbage collection cycles).
JVM tuning can help minimize these pauses.
Nested Classes
What they are: A nested class is a class that is defined inside another class. The class
containing the nested class is called the outer class.
Purpose:
o Logical grouping: To group classes that are logically related and used together.
o Increased encapsulation: Nested classes can access the members (including
private members) of their outer class, even if they are not static. This allows for
tighter control over access and implementation details.
o Code readability and maintainability: Can make code more organized and
easier to understand, especially when a class is only used within the context of
another class.
Types of Nested Classes:
21
They have direct access to the instance variables and methods (both
private and non-private) of the outer class.
To create an instance of an inner class, you first need to create an instance
of the outer class. The syntax is outerObject.new InnerClass().
2. Static Nested Classes:
Declared with the static keyword.
They are associated with the outer class itself, not with any specific
instance of the outer class.
They can only directly access the static members of the outer class. To
access instance members of the outer class, you need to create an instance
of the outer class.
You can create an instance of a static nested class without having an
instance of the outer class. The syntax is OuterClass.StaticNestedClass().
3. Local Inner Classes:
Defined inside a method or a block of code.
Their scope is limited to the block in which they are defined.
They can access the final or effectively final local variables of the
enclosing method or block.
4. Anonymous Inner Classes:
A special type of local inner class that is defined and instantiated at the
same time without an explicit name.
Often used to create simple implementations of interfaces or abstract
classes.
Example
22
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.createInner(); // Creates and uses an InnerClass object
Local Variables
What they are: Local variables are variables declared inside a method, constructor, or
any other block of code (delimited by curly braces {}).
Scope: The scope of a local variable is limited to the block in which it is declared. It
exists only from the point of its declaration until the end of that block. Once the block
finishes executing, the local variable is destroyed, and its memory is reclaimed.
Initialization: Local variables must be initialized before they are used. The compiler
will issue an error if you try to access an uninitialized local variable.
Example
public class Example {
if (true) {
String anotherLocalVar = "Hello"; // Another local variable within the if block
System.out.println(anotherLocalVar);
}
// System.out.println(anotherLocalVar); // Error: anotherLocalVar is out of scope here
}
23
They have a lifetime tied to the object they belong to. They exist as long as the
o
object exists.
o They are automatically initialized to default values if you don't explicitly initialize
them (e.g., 0 for numbers, false for booleans, null for object references).
Example (from before)
Access modifiers are keywords in Java that control the visibility and accessibility of
classes, interfaces, variables, and methods. private and public are two fundamental access
modifiers.
private:
o Members (fields, methods, constructors) declared as private are only accessible
within the class in which they are declared.
o They are not accessible from outside the class, including subclasses or other
classes in the same package.
o Purpose: Enforces data hiding and encapsulation, protecting the internal
implementation details of a class and preventing unintended modifications from
outside.
public:
o Members declared as public are accessible from any other class, in any package.
o Purpose: Provides a way for other parts of the program to interact with the class
and its objects. Public members define the interface of the class.
Other Access Modifiers (for completeness):
o `(default) - No explicit modifier (package-private): Accessible within the same
package but not from outside the package.
o protected: Accessible within the same package and by subclasses (even in different
packages).
24
Parameter Passing Methods
When you call a method, you can pass values (arguments) to the method's parameters.
Java uses pass-by-value for all primitive data types and for references to objects.
Pass-by-Value:
o When you pass a primitive data type (like int, double, boolean), a copy of the actual
value is passed to the method's parameter. Any changes made to the parameter
inside the method do not affect the original variable outside the method.
o When you pass a reference to an object, a copy of the reference (the memory
address) is passed to the method's parameter. Both the original reference and the
parameter reference point to the same object in memory. This means that changes
made to the state (fields) of the object through the parameter reference will
affect the original object. However, if you reassign the parameter reference to
point to a different object inside the method, it will not affect the original
reference outside the method.
Example (Pass-by-Value)
public class ParameterPassing {
25
Before calling modifyPrimitive, myInt = 5
Inside modifyPrimitive: Before modification, num = 5
Inside modifyPrimitive: After modification, num = 10
After calling modifyPrimitive, myInt = 5
Before calling modifyObject, myDog's name = Buddy
Inside modifyObject: Before modification, dog's name = Buddy
Inside modifyObject: After modification, dog's name = Fido
Inside modifyObject: After reassignment, dog's name = Max
After calling modifyObject, myDog's name = Fido
Explanation:
Mathematical Expressions:
1. 3+4×5:
oMultiplication is performed before addition: 4×5=20
oThen, addition: 3+20=23
2. (3+4)×5:
o Parentheses are evaluated first: 3+4=7
o Then, multiplication: 7×5=35
o Notice how the parentheses change the outcome significantly.
3. 10−23+6/2:
o Exponent first: 23=8
o Then, division: 6/2=3
o Finally, subtraction and addition from left to right: 10−8+3=2+3=5
4. 2×512+8:
o The fraction bar acts as a grouping symbol. Evaluate the numerator and
denominator separately first.
o Numerator: 12+8=20
26
o Denominator: 2×5=10
o Then, division: 1020=2
In logic, we have precedence rules for logical operators as well. While the exact order can
sometimes depend on convention, a common order is:
1. ¬A∧B:
o Negation is performed first: (¬A)∧B
2. A∨B∧C:
o Conjunction has higher precedence than disjunction: A∨(B∧C)
3. (A∨B)∧C:
o Parentheses override the default precedence: The OR operation is done before the
AND.
4. P⟹Q∨¬R:
o Negation first: P⟹(Q∨(¬R))
o Then, disjunction: P⟹(Q∨¬R)
o Finally, implication: (P⟹(Q∨¬R))
Understanding precedence rules is crucial for correctly evaluating expressions and ensuring that
they are interpreted as intended. Without these rules, the same expression could yield different
results depending on the order of operations.
print("Hello, world!")
27
2. Simple Input: Getting text from the user
Input: Whatever the user types in after the prompt "What is your name? " and presses
Enter. This input is stored in the variable name.
Output: A greeting that includes the user's name. For example, if the user enters "Alice":
Hello, Alice
Explanation: The input() function displays the prompt inside the parentheses and waits
for the user to type something. The entered text is then returned as a string.
Integers are typically represented using a fixed number of bits (binary digits). Common sizes are
8 bits, 16 bits, 32 bits, and 64 bits. The number of bits determines the range of integers that can
be represented.
Unsigned Integers: These represent only non-negative integers (zero and positive
numbers
Signed Integers: These represent both positive and negative integers.
Real numbers, which include fractions and irrational numbers, are typically represented using the
floating-point standard, most commonly the IEEE 754 standard. This standard defines how
real numbers are stored using a fixed number of bits, usually 32 bits (single-precision) or 64 bits
(double-precision).
±M×BE
Where:
Sign bit (S): A single bit (usually the most significant bit) indicates whether the number
is positive (0) or negative (1).
Mantissa (M) or Significand: Represents the significant digits of the number. It's
usually normalized to have a single non-zero digit before the decimal point (in binary, it's
always 1). The leading '1.' is often implicit (not actually stored) to save a bit, except for
special cases. The fractional part of the mantissa is stored in the allocated bits.
Exponent (E): Represents the power to which the base (B, which is usually 2 in binary
representations) is raised. The exponent is stored with a bias to allow for the
representation of both positive and negative exponents.
28
Single-precision (32 bits):
o 1 bit for the sign
o 8 bits for the exponent (with a bias of 127)
o 23 bits for the fractional part of the mantissa
Double-precision (64 bits):
o 1 bit for the sign
o 11 bits for the exponent (with a bias of 1023)
o 52 bits for the fractional part of the mantissa
Approximation: Most real numbers cannot be represented exactly with a finite number
of bits. This leads to rounding errors and precision limitations.
Special Values: The IEEE 754 standard defines special bit patterns for values like zero,
infinity (positive and negative), and Not-a-Number (NaN) to handle exceptional cases.
Range and Precision: Double-precision floating-point numbers have a much wider
range and higher precision than single-precision numbers due to the larger number of bits
allocated to the exponent and mantissa.
In summary, integers are stored directly as binary numbers, often using two's complement for
signed values. Real numbers are stored using a more complex floating-point representation that
approximates their value using a sign, a mantissa, and an exponent. Understanding these
representations is crucial for comprehending how computers perform numerical calculations and
the potential for precision issues.
1. Boolean Expressions:
At the heart of if statements lie boolean expressions. These are expressions that evaluate to one
of two values: True or False. They are crucial for determining which block of code should be
executed.
Comparison Operators: These operators compare values and return a boolean result.
o == (equal to)
o != (not equal to)
o < (less than)
o > (greater than)
o <= (less than or equal to)
o >= (greater than or equal to)
Logical Operators: These operators combine or modify boolean values.
o and: Returns True if both operands are True.
o or: Returns True if at least one operand is True.
o not: Inverts the boolean value of its operand.
Boolean Variables: Variables that directly hold True or False values.
29
2. if Statement:
The if statement is the fundamental conditional control flow statement. It allows you to execute a
block of code only if a certain condition (a boolean expression) is True.
3. if-else Statement:
The if-else statement allows you to execute one block of code if the condition is True and another
block of code if the condition is False.
How it works: The conditions are checked in order. Once a True condition is found, the
corresponding block of code is executed, and the rest of the elif and else blocks are
skipped. If none of the if or elif conditions are True, and an else block is present, the code
in the else block is executed.
5. Nested if Statements:
Nested if statements occur when you place one if statement (or if-else or if-elif-else) inside another
if statement. This allows for more complex decision-making based on multiple levels of
conditions.
How it works: The outer if condition is evaluated first. If it's True, the code inside its
block is executed, which may include another if statement (the inner if). The inner if
statement's condition is then evaluated.
Important Considerations:
Indentation: In Python (and many other languages), indentation is crucial for defining
the blocks of code associated with if, elif, and else statements. Consistent indentation
makes your code readable and ensures the correct execution flow.
Clarity: While nesting if statements is allowed, deeply nested structures can become hard
to read and maintain. Consider alternative logical structures or breaking down complex
logic into smaller, more manageable parts.
These concepts – boolean expressions and the various forms of if statements – are fundamental to
creating programs that can make decisions and execute different code paths based on specific
conditions. They enable your programs to be dynamic and responsive to different inputs and
situations.
30
LOOPS IN JAVA
Loops are fundamental control flow structures in programming that allow you to repeatedly
execute a block of code as long as a certain condition is met. This saves you from writing the
same code multiple times and makes your programs more efficient and concise.
Most programming languages offer several types of loop statements. We'll focus on the two most
common ones: for loops and while loops, and also touch upon related control flow statements
within loops.
1. for Loops:
for loops
are typically used when you know in advance how many times you want to iterate, or
when you want to iterate over a sequence of items (like a list, string, or range).
2. while Loops:
whileloops are used when you want to repeatedly execute a block of code as long as a certain
condition remains True. The number of iterations is not necessarily known in advance.
31
Important Note: It's crucial to ensure that the condition in a while loop will eventually
become False, otherwise you'll end up with an infinite loop, which will run indefinitely
and potentially crash your program. This usually involves modifying one or more
variables within the loop that are part of the condition.
Within loops, you can use special statements to alter the normal flow of execution:
break Statement: The break statement is used to immediately exit out of the innermost
loop it is contained within. The program control resumes at the statement immediately
following the loop.
continue Statement: The continue statement skips the rest of the code inside the current
iteration of the loop and proceeds to the next iteration.
Iteration Variable: The variable used in a for loop to represent the current item in the
sequence.
Loop Condition: The boolean expression that determines whether a while loop continues
to execute.
Initialization: Before a while loop, you often need to initialize variables that are part of
the loop condition.
Update: Inside a while loop, you must ensure that the variables involved in the loop
condition are updated in a way that will eventually make the condition False, preventing
an infinite loop.
Nested Loops: You can have loops inside other loops (nested loops). The inner loop will
execute completely for each iteration of the outer loop.
Choosing the Right Loop:
o Use for loops when you know the number of iterations or when you're iterating
over a sequence.
o Use while loops when the number of iterations is unknown and depends on a
condition that might change during the loop's execution.
Readability: Write your loop conditions and the code inside the loop in a clear and
understandable way. Use meaningful variable names.
Efficiency: Be mindful of the operations you perform inside a loop, especially if the loop
runs many times, as inefficient code can lead to performance issues.
Loops are a powerful tool in programming, enabling you to automate repetitive tasks and process
collections of data efficiently. Understanding the different types of loops and how to control their
execution is essential for writing effective and robust programs.
Recursion is a programming technique where a method calls itself in its own definition. To avoid
infinite loops, a recursive method must have a base case (a condition that stops the recursion)
32
and a recursive step (where the method calls itself with a modified input that moves closer to
the base case).
a) Factorial Calculation:
33
System.out.println("Sum up to " + num + " is: " + result); // Output: Sum up to 5 is: 15
}
}
Base Case: A recursive method must have one or more base cases that stop the recursion.
Without a proper base case, the method will call itself infinitely, leading to a
StackOverflowError.
Recursive Step: The recursive step should break the problem down into smaller, self-
similar subproblems and move towards the base case.
Stack Usage: Each recursive call adds a new frame to the call stack. Deep recursion can
consume a lot of memory and lead to a StackOverflowError. For some problems, an iterative
(loop-based) solution might be more efficient in terms of memory usage.
Readability: For certain problems, recursion can provide a very elegant and concise
solution that mirrors the problem's structure.
The char data type in Java is a primitive data type used to store a single 16-bit Unicode
character. Unicode is a standard that assigns a unique number to each character in almost all
written languages.
Escape Sequences: Special characters can be represented using escape sequences that
start with a backslash (\):
o \n: Newline
o \t: Tab
o \': Single quote
o \": Double quote
o \\: Backslash
34
Operations: You can perform comparisons (==, !=, <, >, <=, >=) and some arithmetic
operations on char variables, as they are essentially numerical values representing
Unicode code points.
In Java, String, StringBuffer, and StringBuilder are classes used to represent sequences of characters.
However, they have a crucial difference regarding mutability (whether their content can be
changed after creation).
String:
Immutable: String objects are immutable. Once a String object is created, its content
cannot be changed. Any operation that appears to modify a String (like concatenation,
substring, replace) actually creates a new String object. The original String object remains
unchanged.
Thread-Safe: Because String objects cannot be modified after creation, they are
inherently thread-safe, meaning they can be safely shared and used by multiple threads
without the risk of data corruption.
Performance: Due to the creation of new objects for every modification, frequent string
manipulations can be less efficient in terms of performance and memory usage.
StringBuffer:
Mutable: StringBuffer objects are mutable. You can modify the content of a StringBuffer
object after it has been created without creating a new object.
Thread-Safe: StringBuffer is designed to be thread-safe. Its methods are synchronized,
which means that only one thread can access and modify the StringBuffer object at a time.
This makes it safe for use in multithreaded environments where multiple threads might be
accessing the same string data.
Performance: For frequent string manipulations in a multithreaded environment,
StringBuffer can be more efficient than repeatedly creating new String objects. However, the
synchronization overhead can introduce some performance cost compared to StringBuilder.
StringBuilder:
35
Performance: In single-threaded environments, StringBuilder generally offers better
performance than StringBuffer because it avoids the overhead of synchronization.
1. Equality Testing (== Operator): The == operator, when used with reference types (like
String), checks if the two reference variables point to the same object in memory. It compares
the memory addresses of the two objects.
When you create String objects using string literals (enclosed in double quotes), the Java
Virtual Machine (JVM) often uses a mechanism called the "string intern pool." This pool
stores unique string literals. If you create another string literal with the same sequence of
characters, the JVM might reuse the existing String object from the pool, rather than
creating a new one.
In this case, str1 and str2 might refer to the same String object in the intern pool.
When you explicitly create String objects using the new keyword, a new String object is
created in the heap memory, even if another String object with the same content already
exists.
Here, str3 and str4 are distinct String objects in memory, even though they contain the same
sequence of characters. The == operator compares their memory addresses, which are
different.
In summary, == checks for object identity (are they the exact same object in memory?).
2. Equivalence Testing (equals() Method): The equals() method, which is inherited from the
Object class and overridden by the String class, checks if the two String objects have the same
sequence of characters, regardless of whether they are the same object in memory.
36
Comparing Content:
String s1 = "world";
String s2 = new String("world");
System.out.println(s1.equals(s2)); // Output: true (they have the same content)
String s3 = "World";
System.out.println(s1.equals(s3)); // Output: false (case-sensitive comparison)
In summary, equals() checks for content equality (do they have the same characters in the
same order?).
Java
String text1 = "example";
String text2 = "example";
String text3 = new String("example");
This example clearly shows that == can give different results based on how the String objects
were created, while equals() consistently checks for the same content.
37
OBJECTS' PASSING
When you pass a variable of a primitive data type (like int, double, boolean, char) to a method, a
copy of the actual value stored in that variable is created and passed to the method's parameter.
Any changes made to the parameter inside the method do not affect the original variable outside
the method. This is known as pass by value.
As you can see, the originalNum in the main method remains unchanged after calling modifyValue.
When you pass a variable of a reference type (an object) to a method, a copy of the reference
(the memory address where the object is stored) is passed to the method's parameter. Both the
original reference variable and the parameter variable now point to the same object in memory.
This means that if the method modifies the state (fields/attributes) of the object through the
parameter, those changes will be reflected in the original object that the calling code's reference
variable points to. This is often described as pass by value of the reference.
class Dog {
String name;
38
}
}
In this example, the renameDog method receives a reference to the myDog object. When it calls
dogToRename.changeName("Max"), it modifies the name attribute of the actual Dog object in memory,
and this change is visible through the myDog reference in the main method.
If you reassign the parameter variable (the copied reference) to point to a different object within
the method, this will not affect the original reference variable in the calling code. The parameter
is just a copy of the address.
Java
public class PassByReferenceReassign {
public static void changeDogReference(Dog dogToChange) {
dogToChange = new Dog("Lucy"); // 'dogToChange' now points to a new Dog object
System.out.println("Inside method: dogToChange's name = " + dogToChange.name); // Output: Inside method:
dogToChange's name = Lucy
}
39
1. The "Address" is Copied:
When you call a method and provide an object as an argument, the Java Virtual Machine (JVM)
takes the memory address of that object and creates a copy of that address. This copied address is
what the parameter inside the method receives.
2. Both the Original and the Parameter Point to the Same Object:
Because both the original variable in the calling code and the parameter in the method hold the
same memory address (the parameter holds a copy of the address), they both "point" to the exact
same object residing in the heap memory.
If the method uses the parameter reference to modify the attributes (fields, instance variables)
of the object, these modifications are made directly to the object in memory. Since the original
reference in the calling code also points to this same object, you will see these changes reflected
after the method call returns.
Passing a primitive: If you have the number '5' (a primitive) and you tell someone to use
that number, they get a copy of the number '5'. If they change their copy to '10', your
original '5' remains unchanged.
Passing an object (reference): If you have the address of a house (a reference), and you
give a friend a copy of that address, your friend can still go to the same house. If your
friend paints the house a different color, when you go to the house later, you'll see the
new color because it's the same house. However, if your friend gets a different address,
that doesn't change the address you originally had, and it certainly doesn't move or
change your original house.
Code Example (Revisited):
class Car {
String color;
40
Car myCar = new Car("red");
System.out.println("Before method call: myCar is " + myCar.color); // Output: Before method call: myCar is
red
changeCarColor(myCar, "blue");
System.out.println("After method call: myCar is " + myCar.color); // Output: After method call: myCar is blue
}
In this example:
Key Takeaway:
When you pass an object to a method in Java, you are passing a copy of the reference. This
allows the method to interact with and potentially modify the original object. However,
reassigning the reference parameter within the method does not change what the original
reference in the calling code points to.
41
Array and Collection Processing in JAVA
Arrays and Collections are fundamental structures in Java for storing and manipulating groups of
objects. While they both serve this purpose, they have distinct characteristics, advantages, and
use cases.
1. Arrays in Java
An array is a fixed-size, sequential collection of elements of the same data type. Once an array is
created, its size cannot be changed.
Key Characteristics:
Fixed Size: The size of an array is determined at creation and cannot be altered.
Homogeneous: All elements in an array must be of the same data type.
Indexed Access: Elements are accessed using a zero-based index (e.g., arr[0], arr[1]).
Direct Memory Access: Arrays store elements in contiguous memory locations, leading to
efficient access.
Primitive and Object Types: Arrays can hold both primitive data types (like int, double,
boolean) and object references.
// Declaration
int[] numbers;
String[] names;
Accessing Elements:
System.out.println(scores[0]); // Output: 90
System.out.println(fruits[1]); // Output: Banana
// Modifying an element
scores[2] = 88;
System.out.println(scores[2]); // Output: 88
Iterating Arrays:
42
// Using a traditional for loop
for (int i = 0; i < scores.length; i++) {
System.out.print(scores[i] + " ");
}
System.out.println();
// Using enhanced for loop (for-each loop) - preferred for simple iteration
for (String fruit : fruits) {
System.out.print(fruit + " ");
}
System.out.println();
System.out.println(matrix[0][1]); // Output: 2
import java.util.Arrays;
43
int[] copy = Arrays.copyOf(unsorted, 3);
System.out.println(Arrays.toString(copy)); // Output: [1, 2, 5]
Advantages of Arrays
Performance: Faster for random access due to contiguous memory allocation.
Memory Efficiency: Can be more memory-efficient than collections for primitive types.
Disadvantages of Arrays
Fixed Size: Cannot grow or shrink dynamically, leading to potential wasted space or
ArrayIndexOutOfBoundsException.
No Built-in Methods: Lack of rich, built-in methods for common operations (like add,
remove), requiring manual implementation.
44
When you need raw performance for accessing elements.
When dealing with primitive data types to avoid autoboxing overhead.
For multidimensional data structures.
In modern Java development, the Collections Framework is generally preferred over raw arrays
due to its flexibility, rich API, and type safety offered by generics, leading to more robust and
readable code. Arrays are still important for specific performance-critical scenarios or when
integrating with older APIs.
45
EVENT DRIVEN PROGRAMS IN JAVA
Event-driven programming is a fundamental paradigm in software development, particularly
crucial for applications that require user interaction, such as graphical user interfaces (GUIs), or
systems that need to respond to external stimuli. In Java, this paradigm is central to frameworks
like AWT, Swing, and JavaFX.
At its core, event-driven programming is a programming style where the flow of the program is
determined by events. Instead of a program executing a predefined sequence of steps, it waits for
events to occur and then reacts to them
Java's event-handling mechanism is based on the Delegation Event Model. This model
promotes a clean separation of concerns:
46
Steps in the Delegation Event Model:
Java has evolved its GUI frameworks over time, each with its own nuances in event handling,
though the core principles of the Delegation Event Model remain.
AWT was Java's original GUI toolkit. Its event handling is similar to Swing, but Swing
introduced improvements.
Example (AWT - for conceptual understanding, rarely used for new development):
Java
import java.awt.*;
import java.awt.event.*;
Swing is a more comprehensive and flexible GUI toolkit built on top of AWT. It's still widely
used for desktop applications.
Pros: Concise for simple handlers, avoids creating separate files for small classes.
Cons: Can become less readable for complex logic. Cannot be reused easily.
JavaFX is the successor to Swing for developing rich client applications in Java. It provides a
more modern API and better support for styling (CSS), animations, and FXML (for UI
definition). JavaFX also uses the Delegation Event Model, but with a slightly different API.
47
Event class: Base class for all JavaFX events.
EventType: Represents a specific type of event (e.g., MouseEvent.MOUSE_CLICKED,
KeyEvent.KEY_PRESSED).
EventHandler<T extends Event> interface: The generic functional interface used for handling
events. Its single abstract method is handle(T event).
setOnAction(), setOnMouseClicked(), etc.: Convenience methods on nodes (UI components) for
setting a specific event handler. These typically accept a lambda expression or an instance of
EventHandler.
addEventHandler(): A more general method to add multiple handlers for a single event type or to
handle events during different phases (capture or bubbling).
48
INHERITANCE, POLYMORPHISM AND ENCAPSULATION
IN JAVA
In Java, Inheritance, Polymorphism, and Encapsulation are the three fundamental pillars of
Object-Oriented Programming (OOP). They provide a robust framework for building scalable,
maintainable, and flexible applications.
1. Inheritance
Concept: Inheritance is a mechanism where one class (subclass or derived class) acquires the
properties and behaviors (fields and methods) of another class (superclass or base class). It
promotes code reusability and establishes an "is-a" relationship between classes.
Analogy: Think of a family tree. A child inherits traits from their parents. Similarly, in Java, a
Car class might inherit properties like color and speed from a more general Vehicle class.
Key Aspects
Parent/Super/Base Class: The class whose features are inherited.
Child/Sub/Derived Class: The class that inherits features from the parent class.
extends keyword: Used to establish an inheritance relationship.
Code Reusability: Avoids duplicating code by allowing subclasses to reuse methods and fields
from their superclass.
Method Overriding: A subclass can provide a specific implementation for a method that is
already defined in its superclass. This is a key aspect of polymorphism.
super keyword: Used to refer to the superclass's members (fields, methods, and constructors).
Single Inheritance: Java supports single inheritance, meaning a class can inherit from only one
direct superclass.
Multilevel Inheritance: A class can inherit from another class, which in turn inherits from
another class (e.g., A extends B, B extends C).
Hierarchical Inheritance: Multiple subclasses inherit from a single superclass.
Example:
// Superclass
class Vehicle {
String brand;
void honk() {
System.out.println("Vehicle honks!");
}
}
// Subclass
class Car extends Vehicle {
String model;
void drive() {
System.out.println(brand + " " + model + " is driving.");
}
49
2. Polymorphism
Concept: Polymorphism means "many forms." In Java, it refers to the ability of an object
to take on many forms. Specifically, it allows objects of different classes to be treated as
objects of a common type. This is typically achieved through inheritance and interfaces.
Analogy: A person can have many roles: a student, a son/daughter, an employee. Each
role implies different behaviors, but it's still the same person. In Java, a Vehicle reference
can point to a Car object, a Bike object, or any other subclass of Vehicle.
Key Aspects
Compile-time Polymorphism (Method Overloading): Achieved by having multiple
methods with the same name but different parameters (number, type, or order of
arguments) within the same class. The compiler decides which method to call based on
the arguments provided.
Runtime Polymorphism (Method Overriding): Achieved through inheritance and
method overriding. The actual method to be called is determined at runtime based on the
type of the object, not the type of the reference variable. This is also known as dynamic
method dispatch.
Upcasting: Assigning a subclass object to a superclass reference variable (e.g., Vehicle
v = new Car();). This is implicitly allowed.
Downcasting: Assigning a superclass object to a subclass reference variable (e.g., Car
c = (Car) v;). This requires an explicit cast and can lead to ClassCastException if the
object is not actually an instance of the subclass.
instanceof operator: Used to check if an object is an instance of a particular class or
interface.
Example (Runtime Polymorphism):
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks");
}
}
50
public static void main(String[] args) {
Animal myAnimal1 = new Dog(); // Upcasting
Animal myAnimal2 = new Cat(); // Upcasting
Analogy: A car's engine is a complex system of many parts. You don't need to know how every
individual part works to drive the car. You interact with the engine through defined interfaces
like the accelerator pedal and the ignition. Encapsulation works similarly by hiding the internal
workings of a class.
Key Aspects
Data Hiding: Achieved by declaring class fields as private. This prevents direct access and
modification from outside the class.
Getters (Accessor Methods): Public methods that provide read-only access to the private
fields.
Setters (Mutator Methods): Public methods that provide controlled write access to the private
fields. They can include validation logic to ensure data integrity.
51
Information Hiding: The internal implementation details of a class are hidden from
external users. Users interact with the class through its public interface (methods).
Modularity and Maintainability: Changes to the internal implementation of a class
don't affect other parts of the code as long as the public interface remains the same.
Controlled Access: Data can only be accessed or modified through the defined public
methods, allowing for validation and business logic enforcement.
Example:
class BankAccount {
private String accountNumber;
private double balance;
52
System.out.println(amount + " withdrawn. New balance: " + balance);
} else if (amount <= 0) {
System.out.println("Withdrawal amount must be positive.");
} else {
System.out.println("Insufficient funds. Current balance: " + balance);
}
}
}
account.deposit(500.0);
account.withdraw(200.0);
account.withdraw(1500.0); // Will show insufficient funds
account.deposit(-100.0); // Will show error for negative deposit
By understanding and applying these three core OOP principles, Java developers can
create well-structured, robust, and maintainable software systems.
53
USING JAVA SERVLETS AND JAVA SERVER PAGES
Java Servlets and JavaServer Pages (JSP) are two core technologies within the Java EE (now
Jakarta EE) platform, designed for building dynamic web applications. They work together to
handle client requests, process data, and generate dynamic content (typically HTML) to be sent
back to the client's browser.
1. Java Servlets
What is a Servlet?
A Servlet is a Java class that extends the capabilities of a server (typically a web server) by
handling client requests and generating dynamic responses. Servlets are server-side components
that live within a servlet container (e.g., Apache Tomcat, Jetty, WildFly, GlassFish).
54
Session Management: Built-in support for managing user sessions (e.g., using cookies or URL
rewriting).
Request/Response Model: They work on a clear request-response paradigm using
HttpServletRequest to get client data and HttpServletResponse to send data back.
What is JSP?
JSP is a server-side technology that allows developers to embed Java code directly within HTML
(or XML) pages. It's essentially a way to create dynamic web pages by separating the
presentation layer (HTML) from the business logic (Java).
55
Directives (<%@ ... %>): Provide global information about the page.
page: Defines page-dependent attributes (e.g., contentType, import, errorPage).
include: Includes content of another file at translation time.
taglib: Declares a tag library.
Scriptlets (<% ... %>): Allow embedding Java code directly into the JSP.
Avoid excessive use of scriptlets (considered bad practice and "spaghetti code").
Expressions (<%= ... %>): Evaluate a Java expression and output its result directly into the
HTML.
Declarations (<%! ... %>): Declare class-level members (fields or methods) within the
generated servlet.
JSP Actions (<jsp:action />): XML-like tags that control the behavior of the JSP engine (e.g.,
jsp:include, jsp:forward, jsp:useBean).
Comments (<%-- ... --%>): JSP comments are ignored by the JSP engine and not sent to the
client.
Conclusion
Java Servlets and JSP are powerful technologies for building dynamic web applications. Servlets
excel at handling the request/response cycle and business logic, while JSPs are ideal for
presenting dynamic content. When used together in an MVC pattern, they provide a robust and
maintainable architecture for server-side Java web development. While newer frameworks like
56
Spring MVC and Jakarta Faces (JSF) have emerged, understanding Servlets and JSPs is
foundational for any Java web developer.
57
ResultSet: Represents the result of a database query. It's a table of data, and you can
iterate over its rows and access column values.
SQLException: The primary exception class for JDBC errors.
Prerequisites:
Install a Database: You need a database server (e.g., MySQL, PostgreSQL, H2,
SQLite).
Download JDBC Driver: Download the JDBC driver JAR file specific to your database
(e.g., mysql-connector-java.jar for MySQL, postgresql-42.x.x.jar for PostgreSQL).
Add Driver to Classpath: Ensure the JDBC driver JAR is in your project's classpath. If
you're using Maven or Gradle, add it as a dependency in your pom.xml or build.gradle
file.
Error Handling
JDBC operations can throw SQLException. It's essential to handle these exceptions using
try-catch blocks to manage potential database issues (e.g., connection failures, invalid
SQL, data integrity violations).
Advanced JDBC Concepts
Transactions: Grouping multiple SQL statements into a single, atomic unit of work
(either all succeed or all fail). Managed using conn.setAutoCommit(false),
conn.commit(), and conn.rollback().
Batch Processing: Executing multiple SQL statements (especially INSERT, UPDATE,
DELETE) in a single batch to improve performance.
ResultSetMetaData: Provides information about the columns in a ResultSet (e.g.,
column names, types).
DatabaseMetaData: Provides information about the database itself (e.g., tables, stored
procedures, driver capabilities).
Connection Pooling: For production applications, creating a new connection for every
request is inefficient. Connection pooling (e.g., using HikariCP, Apache DBCP, or C3P0)
manages a pool of ready-to-use connections, significantly improving performance.
JDBC is the foundation for almost all Java applications that interact with relational databases.
While higher-level frameworks like JPA (Hibernate, EclipseLink) and Spring JDBC simplify
database access, understanding core JDBC is crucial for debugging, performance tuning, and
specific use cases.
58
GENERAL ENTERPPRISE SOLUTION USING JAVA
Designing a General Enterprise Solution using Java involves leveraging a vast ecosystem of
technologies, frameworks, and architectural patterns to build robust, scalable, secure, and
maintainable applications that meet complex business needs. This isn't about a single piece of
software, but rather a comprehensive system that can address various aspects of a large
organization's operations.
Here's a breakdown of the key concepts, components, and best practices for a general enterprise
solution in Java.
1. Understanding Enterprise Application Characteristics
Enterprise applications typically have:
High Scalability: Ability to handle a large number of concurrent users and transactions.
High Availability: Continuous operation with minimal downtime.
Robustness & Reliability: Resilience to failures and errors.
Security: Protection of sensitive data and access control.
Integration: Ability to communicate with other systems (legacy, third-party,
microservices).
Data Persistence: Storing and retrieving large volumes of data.
Transaction Management: Ensuring data consistency across multiple operations.
Maintainability & Extensibility: Easy to update, fix, and add new features.
Performance: Fast response times and efficient resource utilization.
Distributed Nature: Components often deployed across multiple servers.
While vanilla Java SE provides the language foundation, enterprise solutions heavily rely on
either Jakarta EE (the new open-source name for Java EE) or the Spring Framework, which is
the de facto standard for enterprise Java development today.
Inversion of Control (IoC) / Dependency Injection (DI): Manages object creation and
dependencies, making code more modular and testable.
Aspect-Oriented Programming (AAOP): Enables cross-cutting concerns (logging,
security, transactions) to be applied modularly.
Spring Boot: Rapid application development and deployment framework for Spring. It
auto-configures many things, making it easy to create standalone, production-ready
applications.
59
Spring Data JPA: Simplifies database interaction using the Java Persistence API (JPA)
with minimal boilerplate code.
Spring MVC (or Spring WebFlux): Framework for building web applications (REST
APIs, traditional web apps).
Spring Security: Comprehensive security framework for authentication and
authorization.
Spring Cloud: A set of tools for building distributed systems and microservices.
Spring Integration: For enterprise application integration (EAI) patterns.
Spring Batch: For batch processing.
Simplicity & Ease of Use: Spring Boot especially simplifies setup and deployment.
Flexibility: More flexible and less opinionated than full Jakarta EE application servers.
Microservices Friendly: Excellent tooling for building microservices with Spring Cloud.
Large Community & Ecosystem: Very active development and vast resources.
60
Layered Architecture (N-tier): Separating the application into logical layers
(Presentation, Business Logic, Data Access).
o Presentation Layer: (e.g., Spring MVC, React/Angular/Vue frontend consuming
REST APIs) Handles UI and user interaction.
o Business Logic Layer (Service Layer): (e.g., Spring Services, EJBs) Contains
core business rules and orchestrates operations.
o Data Access Layer (Repository/DAO Layer): (e.g., Spring Data JPA, raw
JDBC) Manages interaction with the database.
o Database Layer: (e.g., relational databases, NoSQL databases).
Server-Side Rendered: Using templating engines like Thymeleaf or JSP (less common
now for complex UIs) with Spring MVC.
Client-Side Rendered (SPA - Single Page Application): Most modern approach.
o JavaScript Frameworks: React, Angular, Vue.js.
o Communicate with the backend via RESTful APIs.
C. Data Persistence:
Databases:
o Relational Databases (SQL): MySQL, PostgreSQL, Oracle, SQL Server. (Most
common for structured data and strong consistency).
o NoSQL Databases: MongoDB (document), Cassandra (column-family), Redis
(key-value), Neo4j (graph). (For specific use cases like high scalability, flexible
schemas, caching).
ORM (Object-Relational Mapping):
o JPA (Java Persistence API): Standard API.
o Hibernate: The most popular JPA implementation.
o Spring Data JPA: Simplifies JPA usage, providing automatic repository
implementations.
JDBC: For direct database interaction in specific cases or when ORM is overkill.
61
RESTful APIs: Synchronous communication between services.
Messaging Queues: (Asynchronous communication)
o JMS (Java Message Service): Standard API for message brokers (e.g.,
ActiveMQ, RabbitMQ).
o Apache Kafka: High-throughput, distributed streaming platform (often used for
event-driven architectures).
o RabbitMQ: Message broker for reliable message delivery.
o Use Cases: Decoupling services, handling background tasks, event sourcing.
gRPC: High-performance RPC framework for inter-service communication (often in
microservices).
Web Services (SOAP/WSDL): Older, more rigid standard for enterprise integration.
Still found in legacy systems.
E. Caching:
G. Testing:
62
Code Quality: SonarQube, Checkstyle, PMD.
API Design: RESTful principles, OpenAPI/Swagger for documentation.
Security Best Practices: Input validation, secure coding guidelines (OWASP Top 10),
least privilege principle.
Error Handling: Consistent and informative error responses.
Scalability Design: Stateless services, horizontal scaling, load balancing.
Resilience: Circuit Breakers (Resilience4j), Retries.
Observability: Metrics, logging, tracing.
63