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

JAVA BASICS AND OOPS

Uploaded by

s14anj
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views

JAVA BASICS AND OOPS

Uploaded by

s14anj
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 83

JAVA

BASICS
AND
OOPS JAVA BASICS
JAVA KEY FEATURES
1. Object-Oriented Programming (OOP)
Java follows the OOP paradigm, which means it focuses on objects and classes. OOP
principles such as inheritance, encapsulation, polymorphism, and abstraction make the
language modular, reusable, and easier to maintain.

2. Platform Independence (WORA)


Thanks to the concept of Write Once, Run Anywhere (WORA), Java programs can run on
any device or operating system that has the Java Virtual Machine (JVM) installed. Java
code is compiled into bytecode, which is platform-agnostic and executed by the JVM on
any platform.

3. Secure
 The Java Security Manager, which defines access rules for Java classes.
 Bytecode verification.

4. Automatic Memory Management (Garbage Collection)


Java uses an automatic memory management system called Garbage Collection. It
automatically handles memory deallocation for unused objects, preventing memory leaks
and reducing the burden of manual memory management.

5. Multithreading(FOR NOW NO NEED)


Java supports multithreading, allowing multiple threads of execution to run concurrently
within a program. This enables the development of highly responsive and efficient
applications, particularly useful for handling tasks like GUI, networking, and file operations
in parallel.
6. High Performance
Although Java is an interpreted language (through the JVM), it achieves high performance
with the help of technologies like Just-In-Time (JIT) compilation. JIT compiles bytecode
into native machine code at runtime, improving execution speed.

7. Rich Standard Library (Java API)(FNNN)


Java comes with an extensive standard library, often referred to as the Java API, which
provides a vast collection of ready-made utilities for:
 Data structures (collections framework)
 Input/output operations (java.io, java.nio)
 Networking (java.net)
 Concurrency (java.util.concurrent)
 GUI development (Swing, AWT, JavaFX)

8. Scalability and Performance


Java is highly scalable and can be used to develop applications ranging from small mobile
apps (Android) to large-scale enterprise solutions (using technologies like Spring,
Hibernate, etc.). The JVM’s optimizations ensure that Java applications perform efficiently
under varying load conditions.

9. Architecture-Neutral
Java bytecode is designed to be architecture-neutral, meaning it is not dependent on the
underlying hardware architecture. This ensures that a Java program will run in the same
way on different hardware platforms.

10. Dynamic and Extensible


Java is a dynamic language because it can dynamically load classes during runtime and
link them as needed. It is also extensible because Java allows developers to use native
code written in other languages (e.g., C or C++) through the Java Native Interface (JNI).

HOW CODE IS CONVERTED INTERNALLY:


JVM (Java Virtual Machine)
The Java Virtual Machine (JVM) is the core component of Java's platform-independent
nature. It is responsible for executing Java programs by converting bytecode (which
is platform-independent) into machine-specific instructions.

JRE (Java Runtime Environment)


Provides everything required to run Java applications, including the JVM and standard libraries.
DK (Java Development Kit):
Provides everything in the JRE along with development tools like the compiler (javac), debugger,
and other utilities to develop, compile, and package Java programs.
METHODS
VARIABLES:
IDENTIFIERS
DATA TYPES:
BYTE,SHORT,CHAR,INT,LONG,FLOAT,DOUBLE
OPERATORS:
DECISION STATEMENT (OR) CONDITIONAL
STATEMENT:
CONTROL STATEMENTS:
LOOPING STATEMENTS:
ACCESS MODIFIERS

DATA MEMBERS:
NON-ACCESS MODIFIERS:
ARGUMENTS:
COMMAND LINE ARGS:
These are typped in command promt,args are provided during
exceution
TYPE CASTING:
WHY IS MAIN CLASS ALWAYS PUBLIC:
 Coz it should be visible for JRE and main method is called by JRE
 Main method is called by JRE w/o creating an object or instance

METHOD OVERLOADING AND RIDING


Feature Method Overloading Method Overriding
Defining multiple methods with the
Redefining a method in a subclass that is
Definition same name but different parameter
already defined in the parent class.
lists.
Must differ in the number, type, orMust have the same parameter list (method
Parameters
order of parameters. signature).
Can have the same or different Must have the same return type as the
Return Type
return types. parent class method.
Cannot reduce the visibility of the method
Access Modifier Can have any access modifier. (e.g., public in parent, cannot be private in
subclass).
Polymorphism Compile-time polymorphism (static Runtime polymorphism (dynamic
Type binding). binding).
No inheritance required; happens Inheritance is necessary; happens between
Inheritance
within the same class. parent and child classes.
Provides multiple ways to perform Allows the subclass to provide its specific
Purpose similar tasks depending on different implementation of a method defined in the
input types. parent class.
The @Override annotation is
Annotation No special annotation required.
recommended but optional.
Useful for creating multiple versions
Use Case Useful for changing or extending the
of a method to handle different input
Example behavior of a parent class in a subclass.
types.
Decided during compile time (static Decided during runtime (dynamic
Execution Time
binding). binding).

ADVANTAGES OF THIS:
Method Overloading enhances flexibility, reduces complexity, and improves code readability and
maintenance through compile-time polymorphism.
Method Overriding provides runtime flexibility, enabling subclasses to modify or extend the
behavior of parent classes, promoting code reusability, abstraction, and adherence to OOP principles
like polymorphism and inheritance. Both techniques are essential for writing robust, reusable, and
extensible Java applications.
RECURSION
Recursion is a programming technique where a function calls itself to solve a problem. The function
typically solves a small part of the problem and then calls itself to solve the remaining parts,
breaking down the problem into smaller subproblems.

Key Characteristics of Recursion:


 Base Case: A condition that stops the recursive calls when the problem is small enough to
be solved directly.
 Recursive Case: The part of the function where the function calls itself to handle a smaller
part of the problem.

THIS KEYWORD AND THIS():


The this keyword refers to the current instance of a class. It is used to distinguish between
instance variables and parameters or to call other methods or constructors within the class.
The this() is a special form of the this keyword used to call another constructor from within
the same class. It helps in constructor chaining, which means one constructor calls another
constructor to reduce code duplication.

SUPER KEYWORD AND SUPER():


The super keyword refers to the parent (superclass) object and is used to access members (fields
and methods) of the superclass.
super() is used to call the constructor of the superclass from the subclass. This is important
because the superclass constructor needs to be called to properly initialize the parent class's fields
before the subclass's constructor executes.

CONSTRUCTORS :
A constructor in Java is a special method that is called when an object of a class is created. It is used
to initialize the object’s state (i.e., assign values to fields or perform startup actions). Unlike regular
methods, constructors have the same name as the class and no return type, not even void.
WHY ONLY CONSTRUCTOR:
Explicitly using constructors in Java is important because they provide a structured, predictable, and
controlled way to initialize objects. While Java offers some default behavior, explicitly defining
constructors ensures that objects are initialized properly, and it gives you more control over the
initialization process.
Avoiding Uninitialized Objects: Ensures that all necessary fields are initialized when the object is
created.
 Immutable Objects: Helps in creating immutable objects that cannot be changed after they
are initialized.
 Flexibility via Overloading: Provides multiple ways to create objects, offering flexibility to
users.
 Control over Initialization: Ensures that objects are initialized with valid, meaningful data.
TYPES OF CONSTRUCTORS:
DEAFULT CONSTRUCTOR
PARAMETERISED CONSTRUCTOR
COPY CONSTRUCTOR:
This constructor is like the behaviour of the car but another copy and we
can change the name also we want

INHERITANCE:
TYPES :
WHY JAVA DOES NOT SUPPORT MULTIPLE INHERITANCE:
Java does not support multiple inheritance through classes to avoid complexity and ambiguity and
diamond problem
Alternate Way :
While Java doesn't allow multiple inheritance with classes, it does allow it using interfaces. An
interface in Java can be implemented by multiple classes, and a class can implement multiple
interfaces. Interfaces don't provide method implementations (at least until Java 8, when default
methods were introduced). This allows Java to support multiple inheritance at the behavioral level
(method signatures) without the ambiguity introduced by class-based inheritance.
ADVANTAGES:
Inheritance is a powerful feature of object-oriented programming that promotes code reuse,
flexibility, and maintainability. It allows for the creation of well-organized class hierarchies, reduces
redundancy, and supports polymorphism, making programs more scalable and easier to maintain in
the long run.

OBJECT CLASS:
The Object class is the root class from which every class directly or indirectly inherits. It is the
parent of all classes, whether user-defined or predefined. If a class doesn't explicitly extend another
class, it implicitly extends the Object class.
protected Object clone()

 Description: Creates and returns a copy (clone) of the object. The cloning
mechanism in Java is shallow by default unless you manually implement deep
copying.
 Note: The class must implement the Cloneable interface; otherwise, it will throw
a CloneNotSupportedException.
java
Copy code
class A implements Cloneable {
int i;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

2. public boolean equals(Object obj)


 Description: Compares the object to another object to check equality. By default, it
compares the reference addresses of two objects (checks if they point to the same
memory location).
 Override: Typically, this method is overridden to compare the values of two objects,
not their references.
java
Copy code
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MyClass myClass = (MyClass) obj;
return this.value == myClass.value;
}

3. public String toString()


 Description: Returns a string representation of the object. The default
implementation returns the class name followed by the "@" symbol and the object's
hash code in hexadecimal.
 Override: Often overridden to provide a more meaningful string representation of an
object's state.
java
Copy code
@Override
public String toString() {
return "MyClass [value=" + value + "]";
}

4. public int hashCode()


 Description: Returns a hash code for the object, which is used in hashing data
structures like HashMap and HashSet.
 Override: If you override the equals() method, you must also override the
hashCode() method to maintain the contract between equals() and
hashCode().
java
Copy code
@Override
public int hashCode() {
return Objects.hash(value);
}

5. protected void finalize()


 Description: Called by the garbage collector when the object is about to be
destroyed. This method allows the object to perform clean-up operations before it is
removed from memory.
 Deprecated: As of JDK 9, finalize() is deprecated because of unpredictability
and performance concerns.
6. public final Class<?> getClass()
 Description: Returns the runtime class of the object, represented by a Class
object. This method helps in reflection to get information about the object's class at
runtime.
java
Copy code
Class<?> cls = obj.getClass();
System.out.println(cls.getName());
7. public final void notify()
 Description: Wakes up one thread that is waiting on the object's monitor. It is used
in multithreading scenarios with synchronization.
 Note: This method should only be called when the thread has a lock on the object
(inside a synchronized block).
8. public final void notifyAll()
 Description: Wakes up all threads that are waiting on the object's monitor, not just
one. All waiting threads will compete for the object's lock once notified.
9. public final void wait()
 Description: Causes the current thread to wait until another thread invokes
notify() or notifyAll() on the object. The thread must own the object's lock
(be inside a synchronized block).
 Overloaded versions:
 wait(long timeout)
 wait(long timeout, int nanos)
10. protected void finalize()
 Description: Called before an object is garbage collected, allowing for resource
clean-up.

Summary of Key Methods:


Method Signature Description
clone() Creates a copy of the object.
equals(Object obj) Compares two objects for equality.
toString() Returns a string representation of the object.
hashCode() Returns a hash code for the object.
getClass() Returns the runtime class of the object.
finalize() Called before garbage collection to clean up resources.
notify() Wakes up one thread waiting on the object.
notifyAll() Wakes up all threads waiting on the object.
wait() Causes the current thread to wait until it is notified.

Usage and Importance:


 equals() and hashCode(): These are essential for comparing objects in collections
like HashMap, HashSet, etc.
 toString(): This is frequently overridden to provide a human-readable representation of
objects, useful for debugging.
 Synchronization Methods (wait(), notify(), notifyAll()): Critical in multi-
threaded programming.

FINAL:
1. Final Variables
A final variable is a constant and can only be assigned once. Once initialized, its value cannot be
changed.
Where can we use final variables?
 Local Variables: Inside methods, constructors, or blocks.
 Instance Variables: Variables that belong to an instance of the class.
 Static Variables: Class-level variables (shared by all instances of the class).
Final Methods
A final method is a method that cannot be overridden by subclasses. Once a method is declared
as final, its implementation is locked and cannot be changed by any subclass.
Where can we use final methods?
 In Any Class: In any class, you can declare certain methods as final to prevent
overriding. This ensures the integrity of that method's behavior across all subclasses.

Final Classes
A final class is a class that cannot be subclassed (i.e., no class can extend it). Declaring a class
as final prevents any class from inheriting from it, ensuring that the class's functionality is
locked.
Where can we use final classes?
 Any Class: Any class that you want to prevent from being extended can be declared as
final. This is especially useful when you want to ensure that no one can modify or extend
the behavior of that class
Final Parameters
A method parameter can be declared as final, meaning that the parameter value cannot be
modified inside the method. However, the final parameter can still be used normally within the
method (for reading).
Where can we use final parameters?
 In Any Method or Constructor: You can mark any method parameter as final to prevent
modification inside the method body.

Context Where It Can Be Used


Final Variables Local variables, instance variables, static variables (constants).
Final Methods Any method in a class that should not be overridden.
Final Classes Any class that should not be extended or subclassed.
Final Parameters Method and constructor parameters to prevent modification.
Final Static Variables Class-level constants shared by all instances of the class.
Final in Anonymous Classes Local variables used inside anonymous inner classes.
IS-A Relationship (Inheritance)
The IS-A relationship represents inheritance in object-oriented programming. If a class inherits
from another class, it is said to have an IS-A relationship with the parent class.
Example:
java
Copy code
class Animal {
public void eat() {
System.out.println("This animal eats.");
}
}

class Dog extends Animal { // Dog IS-A Animal


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

public class Main {


public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // Inherited from Animal
dog.bark();
}
}

 IS-A describes the hierarchical relationship. A Dog IS-A Animal, meaning it inherits
properties and methods of Animal.

HAS-A Relationship (Association)


The HAS-A relationship refers to composition or aggregation. It shows that one object contains or
has another object. In Java, this is typically implemented via instance variables in a class that
reference other objects.
Example:
java
Copy code
class Engine {
public void start() {
System.out.println("Engine starts.");
}
}

class Car { // Car HAS-A Engine


private Engine engine; // Aggregation

public Car() {
this.engine = new Engine(); // Composition
}

public void startCar() {


engine.start(); // Using the Engine object
System.out.println("Car starts.");
}
}

public class Main {


public static void main(String[] args) {
Car car = new Car();
car.startCar();
}
}

 Here, Car HAS-A Engine, meaning Car uses an Engine object to perform certain tasks.

Diamond Problem
The Diamond Problem occurs in multiple inheritance when a class inherits from two classes that
both inherit from the same parent class. This creates ambiguity when the child class attempts to use
a method from the grandparent class, as it doesn’t know which method to inherit.
interface Animal {
void sound();
}

interface Dog extends Animal {


default void sound() {
System.out.println("Dog barks.");
}
}

interface Cat extends Animal {


default void sound() {
System.out.println("Cat meows.");
}
}

class Pet implements Dog, Cat { // Diamond problem in interfaces


// Explicitly overriding sound() to resolve ambiguity
public void sound() {
System.out.println("Pet makes sound.");
}
}

public class Main {


public static void main(String[] args) {
Pet pet = new Pet();
pet.sound(); // Calls overridden method
}
}

Aggregation (Weak HAS-A Relationship)


Aggregation is a weak form of the HAS-A relationship, where the part (component) can exist
independently of the whole. If the whole object is destroyed, the part can still exist.
Example:
java
Copy code
class Student {
private String name;

public Student(String name) {


this.name = name;
}

public String getName() {


return name;
}
}

class School {
private String name;
private List<Student> students; // Aggregation (HAS-A)

public School(String name) {


this.name = name;
this.students = new ArrayList<>(); // Students can exist independently
}

public void addStudent(Student student) {


students.add(student);
}

public List<Student> getStudents() {


return students;
}
}

public class Main {


public static void main(String[] args) {
Student student1 = new Student("John");
School school = new School("ABC School");

school.addStudent(student1); // Adding student to the school


System.out.println(school.getStudents().get(0).getName()); // Output:
John
}
}

 Here, the Student can exist independently of the School, representing aggregation.
Composition (Strong HAS-A Relationship)
Composition is a stronger form of the HAS-A relationship. In composition, the part cannot exist
independently of the whole. If the whole object is destroyed, the part is destroyed as well.
Example:
java
Copy code
class Engine {
public void start() {
System.out.println("Engine starts.");
}
}

class Car { // Composition (HAS-A)


private final Engine engine;

public Car() {
this.engine = new Engine(); // Engine is created within Car, strong
association
}

public void startCar() {


engine.start();
System.out.println("Car starts.");
}
}

public class Main {


public static void main(String[] args) {
Car car = new Car();
car.startCar();
}
}

 Here, Car HAS-A Engine, and the Engine object is tightly coupled with the Car. If the
Car is destroyed, the Engine cannot exist separately, representing composition.

SINGLE TON CLASS:


The Singleton Design Pattern ensures that a class has only one instance and provides a global point
of access to that instance. It is a creational design pattern, primarily used when exactly one object is
needed to coordinate actions across the system.
Key Features of Singleton:
1. Single Instance: Only one instance of the class is created during the entire lifetime of the
application.
2. Global Access: The instance is globally accessible via a static method.
3. Lazy Instantiation: The instance is created only when it's first needed (in some cases).

Implementing a Singleton Class in Java


To implement a Singleton class in Java, we need to follow these steps:
1. Private Constructor: The constructor is made private so that no other class can instantiate
the class directly.
2. Static Method: A public static method provides the instance of the class.
3. Static Instance: A private static instance of the class is maintained, which is returned by the
static method.
Example: Singleton Class for a Database Connection
java
Copy code
class DatabaseConnection {
// Step 1: Create a private static instance of the class
private static DatabaseConnection instance;

// Step 2: Make the constructor private so that it cannot be instantiated


outside
private DatabaseConnection() {
System.out.println("Connecting to the database...");
}

// Step 3: Provide a public static method to return the instance


public static DatabaseConnection getInstance() {
if (instance == null) { // Lazy instantiation
instance = new DatabaseConnection();
}
return instance;
}

public void connect() {


System.out.println("Connection established.");
}
}

public class Main {


public static void main(String[] args) {
// Get the single instance of DatabaseConnection
DatabaseConnection connection1 = DatabaseConnection.getInstance();
connection1.connect(); // Output: Connecting to the database... and
Connection established.

// Try to get another instance


DatabaseConnection connection2 = DatabaseConnection.getInstance();
connection2.connect(); // Output: Connection established (same instance
reused).

// Check if both references point to the same object


System.out.println(connection1 == connection2); // Output: true
}
}

UPCASTING AND DOWNCASTING


Upcasting and Downcasting in Java
In Java, upcasting and downcasting are techniques used in polymorphism and inheritance to
convert types between parent (superclass) and child (subclass). These concepts help in achieving
runtime polymorphism, making Java a flexible and powerful language.
1. Upcasting
Upcasting is when a subclass reference is assigned to a superclass reference. This process is
automatic and implicit, and no explicit casting is required.
In upcasting:
 The subclass object is treated as an object of the parent class.
 This allows the subclass object to be referenced generically by its parent type.
 However, when upcasting, you can only access methods and fields that are defined in the
superclass, even though the object is still an instance of the subclass.
Why Use Upcasting?
 Polymorphism: Upcasting enables polymorphism because you can treat different subclass
objects uniformly as objects of their superclass.
 Decoupling: It reduces dependency on the specific type, making the code more flexible and
easier to extend.
 Object hierarchy handling: It allows you to handle collections or arrays of objects that
share a common parent, regardless of their specific subclass.
Example of Upcasting:
java
Copy code
class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
}

class Dog extends Animal {


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

public void fetch() {


System.out.println("Dog is fetching the ball");
}
}

public class Main {


public static void main(String[] args) {
Animal myAnimal = new Dog(); // Upcasting Dog to Animal
myAnimal.makeSound(); // Output: Bark (Dog's implementation due to
polymorphism)

// myAnimal.fetch(); // Compilation error! Animal reference doesn't


know about fetch().
}
}

Explanation:
 Animal myAnimal = new Dog(); — A Dog object is upcasted to the Animal
reference. The method makeSound() will use the Dog's overridden implementation due to
runtime polymorphism.
 Although the object is of type Dog, you can only access methods defined in the Animal
class using the myAnimal reference.
2. Downcasting
Downcasting is the reverse of upcasting. It is the process of converting a superclass reference back
to a subclass reference. Downcasting requires explicit casting, and it is usually performed when you
need to access specific subclass methods that are not available in the superclass.
In downcasting:
 You cast the superclass reference back to its original subclass type.
 Explicit casting is required because it's a potentially unsafe operation (the superclass
reference may not always be of the subclass type).
Why Use Downcasting?
 Access subclass-specific methods: If a subclass object was upcasted to a superclass, you
can downcast it back to the subclass type to access subclass-specific methods.
 Object processing: In some cases, you may handle objects of different types using
upcasting, but later you need to perform subclass-specific operations, which requires
downcasting.
Example of Downcasting:
java
Copy code
class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
}

class Dog extends Animal {


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

public void fetch() {


System.out.println("Dog is fetching the ball");
}
}

public class Main {


public static void main(String[] args) {
Animal myAnimal = new Dog(); // Upcasting Dog to Animal

myAnimal.makeSound(); // Output: Bark (polymorphism)

// Downcasting back to Dog


Dog myDog = (Dog) myAnimal; // Explicit casting (downcasting)
myDog.fetch(); // Now we can call fetch() because it's a Dog reference
}
}

Explanation:
 Dog myDog = (Dog) myAnimal; — This is an explicit cast to convert the Animal
reference back to Dog.
 After downcasting, you can access the fetch() method that is specific to Dog, which
wasn't possible with the Animal reference.
Unsafe Downcasting Example:
If downcasting is performed incorrectly, it will throw a ClassCastException at runtime.
java
Copy code
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }

public class Main {


public static void main(String[] args) {
Animal myAnimal = new Cat(); // Upcasting Cat to Animal

// Trying to downcast Animal (which is actually a Cat) to Dog


Dog myDog = (Dog) myAnimal; // This will throw ClassCastException
}
}

Explanation:
 In this example, myAnimal is actually of type Cat, but an attempt is made to downcast it
to Dog. This causes a ClassCastException at runtime because the actual object is not
of type Dog.

When to Use Upcasting and Downcasting?


Upcasting:
 When using polymorphism: If you want to process objects of different subclasses through
a common interface (e.g., a collection of animals: List<Animal> containing Dog, Cat,
etc.).
 When you want general behavior: When you don't need to access subclass-specific
methods, you can upcast to keep things general.
Downcasting:
 When you need subclass-specific functionality: If you have upcasted an object but later
need to access subclass-specific behavior, you will need to downcast.
 When using polymorphism but require specific behavior later: You might start
processing objects generically but later need specific behavior, which requires downcasting.

Example: Practical Use of Upcasting and Downcasting


Imagine a scenario where you have different types of vehicles. You may upcast them to a common
superclass (Vehicle) when processing, but later downcast them to perform specific operations
(like car.playRadio() or bike.kickStart()).
java
Copy code
class Vehicle {
public void start() {
System.out.println("Vehicle is starting...");
}
}
class Car extends Vehicle {
public void start() {
System.out.println("Car is starting...");
}

public void playRadio() {


System.out.println("Car radio is playing");
}
}

class Bike extends Vehicle {


public void start() {
System.out.println("Bike is starting...");
}

public void kickStart() {


System.out.println("Bike is being kick-started");
}
}

public class Main {


public static void main(String[] args) {
Vehicle myVehicle = new Car(); // Upcasting Car to Vehicle
myVehicle.start(); // Output: Car is starting...

// Downcasting to access Car-specific methods


Car myCar = (Car) myVehicle;
myCar.playRadio(); // Output: Car radio is playing

// Safe Downcasting Check


if (myVehicle instanceof Bike) {
Bike myBike = (Bike) myVehicle; // This block will not be executed
} else {
System.out.println("This is not a bike."); // Output: This is not a
bike.
}
}
}

Key Concepts:
 Upcasting: Vehicle myVehicle = new Car(); allows us to handle Car objects in
a generic manner.
 Downcasting: Car myCar = (Car) myVehicle; allows us to regain access to
methods specific to the Car class (like playRadio()).
 instanceof check: Always check using instanceof before downcasting to avoid
ClassCastException.

Benefits of Upcasting and Downcasting


Advantages of Upcasting:
1. Simplifies code: Allows you to treat subclasses as instances of the parent class, making it
easier to work with groups of related objects.
2. Promotes Polymorphism: Enables runtime polymorphism, allowing you to write more
flexible and reusable code.
3. Encapsulation: Allows you to hide the implementation details of a subclass, providing
access only to the methods available in the superclass.
Advantages of Downcasting:
1. Enables Specific Behavior: Allows you to retrieve subclass-specific functionality after
working generically with the superclass.
2. More Control: Useful in situations where you want to temporarily treat an object as its
parent class, then revert to its specific type for specialized tasks.

Conclusion
 Upcasting and downcasting are integral parts of polymorphism in Java. Upcasting allows
treating objects of derived classes as objects of a base class, while downcasting gives access
to subclass-specific behavior.
 These techniques help make code flexible, reusable, and decoupled by allowing different
objects to be processed uniformly.
 However, downcasting requires caution to avoid ClassCastException at runtime.
Always use instanceof checks to ensure safe downcasting.

BINDING:
Binding in Java
In programming, binding refers to the process of associating a method call or a variable reference
with its corresponding code or value. In Java, binding determines which method or variable a name
refers to during program execution. There are two main types of binding:
1. Static Binding (Early Binding)
2. Dynamic Binding (Late Binding)
Understanding these types of binding is crucial for grasping concepts like polymorphism, method
overloading, and method overriding in Java.

1. Static Binding (Early Binding)


Static Binding occurs when the binding of a method call or variable is resolved at compile-time.
This means the compiler determines the method to be invoked or the variable to be accessed based
on the reference type, not the actual object.
Characteristics of Static Binding:
 Binding Time: Compile-time
 Applies To:
 Static methods
 Private methods
 Final methods
 Method overloading
 Variables (fields)
 Cannot Be Overridden: These methods cannot be overridden in subclasses.
 Performance: Faster execution since the decision is made at compile-time.
Examples of Static Binding:
Example 1: Static Methods
java
Copy code
class Parent {
public static void display() {
System.out.println("Static method in Parent");
}
}

class Child extends Parent {


public static void display() {
System.out.println("Static method in Child");
}
}

public class Main {


public static void main(String[] args) {
Parent obj = new Child();
obj.display(); // Output: Static method in Parent
}
}

Explanation:
 Even though obj is a reference to a Child object, the display() method from Parent
is called because static methods are statically bound based on the reference type (Parent),
not the object type (Child).
Example 2: Variables
java
Copy code
class Parent {
int x = 10;
}

class Child extends Parent {


int x = 20;
}

public class Main {


public static void main(String[] args) {
Parent obj = new Child();
System.out.println(obj.x); // Output: 10
}
}

Explanation:
 Variables are statically bound. Even though obj refers to a Child object, obj.x accesses
the variable x defined in Parent, outputting 10.

2. Dynamic Binding (Late Binding)


Dynamic Binding occurs when the binding of a method call is resolved at runtime. The JVM
determines the method to be invoked based on the actual object's type, not the reference type.
Characteristics of Dynamic Binding:
 Binding Time: Runtime
 Applies To:
 Overridden instance methods
 Supports Polymorphism: Allows for runtime polymorphism through method overriding.
 Performance: Slightly slower due to runtime decision-making.
Examples of Dynamic Binding:
Example 1: Method Overriding
java
Copy code
class Parent {
public void display() {
System.out.println("Instance method in Parent");
}
}

class Child extends Parent {


@Override
public void display() {
System.out.println("Instance method in Child");
}
}

public class Main {


public static void main(String[] args) {
Parent obj = new Child();
obj.display(); // Output: Instance method in Child
}
}

Explanation:
 Here, display() is an instance method overridden in Child.
 The method call obj.display() is dynamically bound at runtime to Child's
display() method because obj refers to a Child object.

Key Differences Between Static and Dynamic Binding


Aspect Static Binding Dynamic Binding
Binding Time Compile-time Runtime
Static, final, and private methods;
Applies To Overridden instance methods
variables
Method Overriding Not applicable Applicable
Slightly slower (decided at
Performance Faster (decided at compile-time)
runtime)
Polymorphism Does not support runtime polymorphism Supports runtime polymorphism
Reference Type Used Based on reference type Based on actual object type
Detailed Examples
Static Binding with Final Methods
java
Copy code
class Parent {
public final void show() {
System.out.println("Final method in Parent");
}
}

class Child extends Parent {


// This would cause a compile-time error:
// Cannot override the final method from Parent
// public void show() {
// System.out.println("Attempting to override final method in Child");
// }
}

public class Main {


public static void main(String[] args) {
Parent obj = new Child();
obj.show(); // Output: Final method in Parent
}
}

Explanation:
 Final methods cannot be overridden, so show() in Parent is statically bound.
Dynamic Binding with Interfaces
java
Copy code
interface Animal {
void sound();
}

class Cat implements Animal {


public void sound() {
System.out.println("Cat meows");
}
}

class Dog implements Animal {


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

public class Main {


public static void main(String[] args) {
Animal animal1 = new Cat();
animal1.sound(); // Output: Cat meows

Animal animal2 = new Dog();


animal2.sound(); // Output: Dog barks
}
}

Explanation:
 Animal is an interface with a method sound().
 Both Cat and Dog implement sound() differently.
 The method calls are dynamically bound to the actual object's sound() method at runtime.

Why Is Binding Important?


 Polymorphism: Dynamic binding is essential for achieving polymorphism, allowing
methods to behave differently based on the object's actual type.
 Maintainability: Understanding binding helps in writing maintainable and error-free code,
especially when dealing with inheritance hierarchies.
 Performance Optimization: Knowing when binding occurs can aid in performance tuning,
as static binding is faster than dynamic binding.

Practical Applications
Using Polymorphism with Dynamic Binding
java
Copy code
abstract class Employee {
public abstract void work();
}

class Developer extends Employee {


public void work() {
System.out.println("Developer is coding");
}
}

class Manager extends Employee {


public void work() {
System.out.println("Manager is planning");
}
}

public class Main {


public static void main(String[] args) {
Employee emp1 = new Developer();
Employee emp2 = new Manager();

emp1.work(); // Output: Developer is coding


emp2.work(); // Output: Manager is planning
}
}

Explanation:
 Both emp1 and emp2 are of type Employee but refer to different subclasses.
 The work() method is dynamically bound to the respective subclass implementations at
runtime.

Considerations and Best Practices


 Method Overloading vs. Overriding:
 Overloading is an example of static binding (methods with the same name but
different parameters).
 Overriding involves dynamic binding (subclass provides a specific implementation
of a method in the parent class).
 Avoid Confusion with Static Methods:
 Static methods are bound at compile-time.
 They are associated with the class, not instances, and cannot be overridden, only
hidden.
 Use Annotations:
 The @Override annotation helps prevent errors by ensuring you're actually
overriding a method.

Summary
 Static Binding:
 Occurs at compile-time.
 Associated with static, final, and private methods, as well as variables.
 Faster execution but less flexible.
 Dynamic Binding:
 Occurs at runtime.
 Associated with overridden instance methods.
 Enables polymorphism and flexible code design.

ABSTRACT:
1. Abstract Class
An abstract class is a class that cannot be instantiated on its own and is meant to be subclassed. It
can contain both abstract methods (methods without a body) and concrete methods (methods with
an implementation).
Key Features of Abstract Classes:
 Cannot be instantiated: You cannot create objects of an abstract class directly.
 Can contain abstract methods: These are methods declared without an implementation.
 Can contain concrete methods: These are regular methods with an implementation.
 Can have constructors: An abstract class can have constructors that are called when an
instance of a subclass is created.
 Can have member variables: Abstract classes can have fields (attributes) to store data.
Syntax of an Abstract Class:
java
Copy code
abstract class AbstractClassName {
// Abstract method (without a body)
abstract void abstractMethod();

// Concrete method
void concreteMethod() {
System.out.println("This is a concrete method.");
}
}

Example of an Abstract Class:


java
Copy code
abstract class Animal {
// Abstract method (no body)
abstract void makeSound();

// Concrete method
void eat() {
System.out.println("This animal eats food.");
}
}

class Dog extends Animal {


// Implementing the abstract method
@Override
void makeSound() {
System.out.println("Dog barks.");
}
}

class Cat extends Animal {


// Implementing the abstract method
@Override
void makeSound() {
System.out.println("Cat meows.");
}
}

public class Main {


public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();

myDog.makeSound(); // Output: Dog barks.


myCat.makeSound(); // Output: Cat meows.

myDog.eat(); // Output: This animal eats food.


myCat.eat(); // Output: This animal eats food.
}
}

Explanation:
 The Animal class is an abstract class that has one abstract method makeSound() and one
concrete method eat().
 The Dog and Cat classes are concrete subclasses that extend Animal and provide
implementations for the makeSound() method.
 In the Main class, we can create instances of Dog and Cat, but we cannot create an
instance of Animal.
2. Abstract Method
An abstract method is a method that is declared without an implementation. This means that
subclasses must provide an implementation for this method.
Key Features of Abstract Methods:
 No body: An abstract method does not have a body; it ends with a semicolon.
 Must be in an abstract class: Abstract methods can only exist in abstract classes.
 Subclass implementation required: Any subclass of an abstract class must implement all
abstract methods, or it must also be declared abstract.
Syntax of an Abstract Method:
java
Copy code
abstract void methodName();

Example of an Abstract Method:


The previous example already demonstrates the concept of abstract methods through the
makeSound() method in the Animal class.

3. Benefits of Using Abstract Classes and Methods


 Code Reusability: Abstract classes allow you to define common methods and attributes that
can be reused in subclasses.
 Separation of Concerns: They enable you to separate the implementation of a method from
its definition, allowing different subclasses to implement it differently.
 Polymorphism: Abstract classes facilitate polymorphic behavior, where a reference variable
can point to objects of different classes.

4. When to Use Abstract Classes


 When you want to define a template for a group of related classes.
 When you have some common functionality that should be shared among subclasses.
 When you want to enforce a contract for subclasses to implement specific methods.

5. Abstract Classes vs. Interfaces


 Abstract Class:
 Can have both abstract and concrete methods.
 Can have member variables and constructors.
 Supports single inheritance (a class can extend only one abstract class).
 Interface:
 Can only have abstract methods (in Java 7 and earlier), but Java 8 onwards allows
default and static methods.
 Cannot have instance variables (only constants).
 Supports multiple inheritance (a class can implement multiple interfaces).

Example of Interface:
java
Copy code
interface Animal {
void makeSound();
void eat();
}

class Dog implements Animal {


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

public void eat() {


System.out.println("Dog eats meat.");
}
}

class Cat implements Animal {


public void makeSound() {
System.out.println("Cat meows.");
}

public void eat() {


System.out.println("Cat eats fish.");
}
}

public class Main {


public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();

myDog.makeSound(); // Output: Dog barks.


myCat.makeSound(); // Output: Cat meows.

myDog.eat(); // Output: Dog eats meat.


myCat.eat(); // Output: Cat eats fish.
}
}

Summary
 Abstract Classes: Used when you want to define a base class with some common
functionality but still want subclasses to provide specific implementations for certain
methods.
 Abstract Methods: Define a method that must be implemented by any non-abstract
subclass.
 Abstraction in Java enhances code organization, reusability, and maintainability, providing a
clear structure for software design.

COUPLING
Loose Coupling
Loose coupling refers to a design principle in which components (modules or classes) have
minimal dependencies on each other. In a loosely coupled system, changes in one module do not
significantly affect other modules, leading to improved maintainability, scalability, and flexibility.
Characteristics of Loose Coupling:
 Independence: Modules can be developed, tested, and updated independently.
 Flexibility: Easier to replace or modify components without affecting the entire system.
 Interchangeability: New implementations can be easily substituted for old ones without
changing the dependent code.
 Reduced Impact of Changes: Changes in one module have little to no effect on other
modules.
Example of Loose Coupling
Using interfaces is a common way to achieve loose coupling. Below is an example illustrating loose
coupling in Java using interfaces.
java
Copy code
// Define an interface
interface Payment {
void pay(int amount);
}

// Implementation of CreditCard payment


class CreditCardPayment implements Payment {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card.");
}
}

// Implementation of PayPal payment


class PayPalPayment implements Payment {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal.");
}
}

// Order class using loose coupling


class Order {
private Payment payment;

// Constructor Injection (Dependency Injection)


public Order(Payment payment) {
this.payment = payment;
}

public void processOrder(int amount) {


payment.pay(amount); // Calls pay method of the Payment interface
}
}

public class Main {


public static void main(String[] args) {
Payment paymentMethod = new CreditCardPayment(); // Can switch to
PayPalPayment easily
Order order = new Order(paymentMethod);
order.processOrder(100);
}
}

Benefits of Loose Coupling:


1. Ease of Maintenance: Since modules are independent, maintaining and updating them is
easier.
2. Enhanced Testability: You can test modules in isolation, leading to better unit testing.
3. Improved Reusability: Loosely coupled components can be reused in different contexts
without modification.

Tight Coupling
Tight coupling refers to a design principle where components are highly dependent on each other.
In tightly coupled systems, changes in one module can significantly impact other modules, making
the system rigid and harder to maintain.
Characteristics of Tight Coupling:
 Dependency: Modules are highly dependent on each other, often directly referencing
specific implementations.
 Limited Flexibility: Changing one module usually requires changes in dependent modules.
 Difficult Testing: Testing individual modules becomes challenging due to their
interdependencies.
 Lower Reusability: Modules are often designed for specific contexts, making them less
reusable.
Example of Tight Coupling
In this example, a class directly instantiates another class, demonstrating tight coupling.
java
Copy code
class CreditCardPayment {
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card.");
}
}

class Order {
private CreditCardPayment creditCardPayment;

public Order() {
// Directly creating a CreditCardPayment instance
this.creditCardPayment = new CreditCardPayment();
}

public void processOrder(int amount) {


creditCardPayment.pay(amount); // Calls the pay method
}
}

public class Main {


public static void main(String[] args) {
Order order = new Order();
order.processOrder(100);
}
}

Drawbacks of Tight Coupling:


1. High Maintenance Cost: Changes in one class often lead to changes in others, increasing
maintenance costs.
2. Difficulty in Testing: Testing classes in isolation is challenging due to their
interdependencies.
3. Limited Flexibility: It’s harder to swap out implementations or make changes without
affecting the entire system.

Summary of Loose vs. Tight Coupling


Aspect Loose Coupling Tight Coupling
Dependencies Minimal dependencies High dependencies
Flexibility High flexibility and interchangeability Low flexibility; changes require more effort
Maintenance Easier to maintain More challenging to maintain
Testing Easier to test independently Difficult to test in isolation
Reusability Higher reusability Lower reusability

1. Encapsulation
Definition
Encapsulation is the bundling of data (attributes) and methods (functions or behaviors) that operate
on the data into a single unit, usually a class. It restricts direct access to some of an object’s
components, which is a means of preventing unintended interference and misuse of the methods and
data.

Characteristics
 Access Modifiers: Encapsulation utilizes access modifiers (private, protected, public) to
control visibility and access to class members.
 Data Hiding: By making class variables private, you can hide the internal state of the object
and expose only necessary methods.
 Getter and Setter Methods: These methods allow controlled access to the private data of a
class.

Benefits
 Control: Encapsulation provides control over the data by allowing validation and other logic
to be implemented in getter and setter methods.
 Maintainability: Changes to the internal implementation of a class can be made without
affecting external code that uses the class.
 Improved Security: Sensitive data can be hidden from outside access.

Example
java
Copy code
class BankAccount {
// Private variables
private String accountNumber;
private double balance;

// Constructor
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}

// Getter method for account number


public String getAccountNumber() {
return accountNumber;
}

// Getter method for balance


public double getBalance() {
return balance;
}

// Method to deposit money


public void deposit(double amount) {
if (amount > 0) {
balance += amount;
} else {
System.out.println("Deposit amount must be positive!");
}
}

// Method to withdraw money


public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
} else {
System.out.println("Insufficient funds or invalid amount!");
}
}
}

public class Main {


public static void main(String[] args) {
BankAccount account = new BankAccount("123456789", 500.0);
account.deposit(150.0);
System.out.println("Balance: " + account.getBalance());
account.withdraw(100.0);
System.out.println("Balance after withdrawal: " + account.getBalance());
}
}

Explanation
 The BankAccount class encapsulates the account number and balance.
 The attributes accountNumber and balance are private, meaning they cannot be
accessed directly outside the class.
 The class provides public methods to deposit and withdraw money, ensuring the balance
remains valid.
2. Polymorphism
Definition
Polymorphism means "many forms." It allows methods to do different things based on the object it
is acting upon. In Java, polymorphism can be achieved through method overloading and method
overriding.

Characteristics
 Method Overloading: This occurs when multiple methods in the same class have the same
name but different parameter lists (types, number, or both).
 Method Overriding: This occurs when a subclass provides a specific implementation of a
method that is already defined in its superclass.

Benefits
 Flexibility and Reusability: Polymorphism allows methods to be used in different contexts,
enhancing code flexibility and reusability.
 Dynamic Method Resolution: In the case of method overriding, Java resolves the method
to be executed at runtime based on the object’s type.

Example
Method Overloading
java
Copy code
class MathOperations {
// Method to add two integers
public int add(int a, int b) {
return a + b;
}

// Method to add three integers


public int add(int a, int b, int c) {
return a + b + c;
}

// Method to add two doubles


public double add(double a, double b) {
return a + b;
}
}

public class Main {


public static void main(String[] args) {
MathOperations math = new MathOperations();
System.out.println("Sum of two integers: " + math.add(5, 10)); //
Output: 15
System.out.println("Sum of three integers: " + math.add(5, 10, 15)); //
Output: 30
System.out.println("Sum of two doubles: " + math.add(5.5, 10.5)); //
Output: 16.0
}
}

Method Overriding
java
Copy code
class Animal {
public void sound() {
System.out.println("Animal makes sound");
}
}

class Dog extends Animal {


@Override
public void sound() {
System.out.println("Dog barks");
}
}

class Cat extends Animal {


@Override
public void sound() {
System.out.println("Cat meows");
}
}

public class Main {


public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();

myDog.sound(); // Output: Dog barks


myCat.sound(); // Output: Cat meows
}
}

Explanation
 In the first example, the MathOperations class demonstrates method overloading with
multiple add methods.
 In the second example, Animal is the superclass with a sound method, which is
overridden by Dog and Cat subclasses, allowing for specific implementations.

3. Abstraction
Definition
Abstraction is the concept of hiding the complex implementation details of a system and exposing
only the necessary parts. In Java, abstraction is achieved using abstract classes and interfaces.

Characteristics
 Abstract Classes: An abstract class cannot be instantiated and may contain abstract methods
(without implementation) and concrete methods (with implementation).
 Interfaces: An interface can only contain method signatures and constants. Since Java 8,
interfaces can also have default and static methods.

Benefits
 Simplification: Abstraction simplifies complex systems by allowing users to interact with
simpler interfaces.
 Maintainability: Changes to the implementation can be made without affecting the code
that uses the abstraction.
 Code Reusability: Common functionality can be defined in an abstract class or interface,
promoting reusability across different classes.

Example
Using Abstract Classes
java
Copy code
abstract class Shape {
abstract void draw(); // Abstract method

public void display() {


System.out.println("Displaying shape");
}
}

class Circle extends Shape {


@Override
void draw() {
System.out.println("Drawing Circle");
}
}

class Rectangle extends Shape {


@Override
void draw() {
System.out.println("Drawing Rectangle");
}
}

public class Main {


public static void main(String[] args) {
Shape circle = new Circle();
Shape rectangle = new Rectangle();

circle.draw(); // Output: Drawing Circle


rectangle.draw(); // Output: Drawing Rectangle
}
}

Using Interfaces
java
Copy code
interface Drawable {
void draw(); // Abstract method
}

class Circle implements Drawable {


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

class Rectangle implements Drawable {


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

public class Main {


public static void main(String[] args) {
Drawable circle = new Circle();
Drawable rectangle = new Rectangle();

circle.draw(); // Output: Drawing Circle


rectangle.draw(); // Output: Drawing Rectangle
}
}

Explanation
 In the first example, the abstract class Shape defines an abstract method draw that must be
implemented by subclasses like Circle and Rectangle.
 In the second example, the Drawable interface defines a method draw, which is
implemented by both Circle and Rectangle, demonstrating abstraction.

Summary
 Encapsulation: Bundles data and methods that operate on the data into a single unit and
restricts access to some components, promoting data hiding and controlled access.
 Polymorphism: Allows methods to behave differently based on the object they are acting
upon, achieved through method overloading and overriding.
 Abstraction: Hides complex implementation details and exposes only the necessary
features, making systems easier to use and maintain.

GETTER AND SETTER METHODS


Getter Method: A method that retrieves (or "gets") the value of a private attribute. It usually has a
return type that matches the type of the property it is retrieving.
 Setter Method: A method that sets (or "updates") the value of a private attribute. It usually
has a void return type and takes a parameter that matches the type of the property it is
setting.

Why Use Getter and Setter Methods?


Encapsulation: By making class fields private, you can restrict direct access to them. Getters and
setters provide a controlled way to access and modify the data.
class Person {
// Private attributes
private String name;
private int age;

// Getter for name


public String getName() {
return name;
}

// Setter for name


public void setName(String name) {
this.name = name; // Optionally, you can validate the name
}

// Getter for age


public int getAge() {
return age;
}

// Setter for age with validation


public void setAge(int age) {
if (age >= 0) { // Validate age
this.age = age;
} else {
System.out.println("Age cannot be negative.");
}
}
}

public class Main {


public static void main(String[] args) {
Person person = new Person();

// Using setters to set values


person.setName("Alice");
person.setAge(30);

// Using getters to retrieve values


System.out.println("Name: " + person.getName()); // Output: Name: Alice
System.out.println("Age: " + person.getAge()); // Output: Age: 30

// Attempting to set an invalid age


person.setAge(-5); // Output: Age cannot be negative.
}
}

PARSE METHOD:
The parse method in Java is commonly associated with converting a String representation of
data into its respective primitive data type or an object of a certain class. This is particularly useful
for handling user inputs, reading data from files, or converting data formats.
The parse method is not a single method but is implemented in various classes within the Java
standard library, such as Integer, Double, Boolean, etc. Each of these classes provides static
parse methods to convert a String to the corresponding primitive type.
1. Integer.parseInt(String s):

 Converts a String to an int.


 Throws NumberFormatException if the string cannot be parsed as an integer.
java
Copy code
String numberString = "123";
int number = Integer.parseInt(numberString);
System.out.println("Parsed Integer: " + number);

CLONING AND ITS TYPES:


In Java, cloning refers to the process of creating an exact copy of an object. This concept is essential
when you want to duplicate an object without affecting the original object. Java provides a built-in
mechanism for cloning via the Cloneable interface and the clone() method in the Object
class.

Types of Cloning
There are two primary types of cloning in Java:
1. Shallow Cloning
2. Deep Cloning
Let's explore each type in detail.
1. Shallow Cloning
Shallow cloning creates a new object that is a copy of the original object, but it only copies the
references to the objects (not the objects themselves) that the original object references. As a result,
if the original object contains references to mutable objects, changes made to those mutable objects
via one object will be reflected in the other.
Example of Shallow Cloning
java
Copy code
class Address {
String city;

Address(String city) {
this.city = city;
}
}

class Person implements Cloneable {


String name;
Address address; // Reference type

Person(String name, Address address) {


this.name = name;
this.address = address;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // Shallow copy
}
}

public class ShallowCloneExample {


public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person original = new Person("Alice", address);

// Cloning the original object


Person cloned = (Person) original.clone();

System.out.println("Original: " + original.name + ", " +


original.address.city);
System.out.println("Cloned: " + cloned.name + ", " +
cloned.address.city);

// Changing the city in the cloned object


cloned.address.city = "Los Angeles"; // This affects the original's
address too

System.out.println("\nAfter modifying cloned object:");


System.out.println("Original: " + original.name + ", " +
original.address.city); // Affected
System.out.println("Cloned: " + cloned.name + ", " +
cloned.address.city); // Modified
}
}

Output
vbnet
Copy code
Original: Alice, New York
Cloned: Alice, New York

After modifying cloned object:


Original: Alice, Los Angeles
Cloned: Alice, Los Angeles

Explanation:
 In the example above, when the city of the cloned object's address is changed, it also reflects
in the original object since they both reference the same Address object.

2. Deep Cloning
Deep cloning creates a new object and also recursively copies all objects that are referenced by the
original object. This means that changes made to mutable objects in the cloned object do not affect
the original object.
Example of Deep Cloning
java
Copy code
class Address {
String city;

Address(String city) {
this.city = city;
}

// Deep copy method


public Address clone() {
return new Address(this.city);
}
}

class Person implements Cloneable {


String name;
Address address; // Reference type

Person(String name, Address address) {


this.name = name;
this.address = address;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone(); // Shallow copy
cloned.address = this.address.clone(); // Deep copy for Address
return cloned;
}
}

public class DeepCloneExample {


public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person original = new Person("Alice", address);

// Cloning the original object


Person cloned = (Person) original.clone();
System.out.println("Original: " + original.name + ", " +
original.address.city);
System.out.println("Cloned: " + cloned.name + ", " +
cloned.address.city);

// Changing the city in the cloned object


cloned.address.city = "Los Angeles"; // This does NOT affect the
original's address

System.out.println("\nAfter modifying cloned object:");


System.out.println("Original: " + original.name + ", " +
original.address.city); // Not affected
System.out.println("Cloned: " + cloned.name + ", " +
cloned.address.city); // Modified
}
}

Output
vbnet
Copy code
Original: Alice, New York
Cloned: Alice, New York

After modifying cloned object:


Original: Alice, New York
Cloned: Alice, Los Angeles

Explanation:
 In this example, the Address class has a clone method that creates a new Address
object with the same city. When the cloned Person object's address is modified, it does not
affect the original object.

Conclusion
 Shallow Cloning: Creates a new object and copies references of the original object's fields,
leading to shared references for mutable objects.
 Deep Cloning: Creates a new object and recursively copies all objects referenced by the
original object, resulting in completely independent objects.

Cloning in Java Summary


 To clone an object in Java, a class must implement the Cloneable interface and override
the clone() method.
 Use shallow cloning when shared references are acceptable and deep cloning when full
independence is required.
 Be cautious with mutable objects; use deep cloning to avoid unintended side effects.
INTERFACE:
WRAPPER CLASSES

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy