Java Interview Questions for 10 Years Experience
- Recommended to read and brush up for your interview:
- Must know interview questions and concept for any Java Interview
- Spring boot interview Questions for 10 years experiance
Explain the Java Memory Model in detail, what is happens-before,Volatile Variables,Memory Visibility? Also give example.
The Java Memory Model (JMM) defines how threads in a multi-threaded Java program interact with memory and each other. Understanding JMM is crucial for ensuring thread safety in high-concurrency scenarios.
Concepts in the Java Memory Model: 1. Happens-Before Relationship:The "happens-before" relationship is a key concept in JMM. It establishes a partial order among memory operations. If one operation happens before another, the effects of the first operation are visible to the second. For example, if you write to a variable (Operation A) and then read from it (Operation B) and Operation A happens before Operation B, the value written by Operation A is guaranteed to be visible to Operation B.
2. Volatile Variables:A variable declared as volatile ensures a "happens-before" relationship for read and write operations on that variable. When a thread writes to a volatile variable, it flushes its local memory to main memory, ensuring that other threads see the updated value when they read the variable. Volatile variables are useful for simple synchronization scenarios, but they may not be sufficient for complex operations that require atomicity.
3. Memory Visibility:Memory visibility is the property that ensures changes made by one thread to shared variables are visible to other threads. Without proper synchronization, changes made by one thread may not be visible to others, leading to data inconsistencies and bugs. The "happens-before" relationship and synchronization mechanisms like locks and monitors ensure memory visibility.
Ensuring Thread Safety:To ensure thread safety in Java applications, especially in high-concurrency scenarios, you can follow these principles:
1. Use Synchronization:Use synchronized blocks or methods to protect critical sections of code. This ensures that only one thread can access the synchronized block at a time, preventing concurrent modification of shared resources.
package codeKatha;
public class SynchronizationExample {
private int sharedVariable = 0;
public synchronized void increment() {
sharedVariable++;
}
}
2. Use Volatile Variables:
Declare variables as volatile when they are shared among multiple threads and need to be accessed and updated safely.
package codeKatha;
public class VolatileExample {
private volatile boolean flag = false;
public void toggleFlag() {
flag = !flag;
}
}
3. Use Thread-Safe Data Structures:
Prefer using thread-safe data structures from the `java.util.concurrent` package, such as `ConcurrentHashMap` or `ConcurrentLinkedQueue`, when dealing with shared collections or queues.
4. Leverage Atomic Operations:Use atomic classes like `AtomicInteger` or `AtomicLong` to perform compound actions atomically without the need for locks or synchronized blocks.
package codeKatha;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
}
Discuss the differences between the Serializable and Externalizable interfaces in Java. When would you use one over the other for object serialization, and what are the potential performance implications?
In Java, the Serializable and Externalizable interfaces are used for object serialization, but they differ in terms of customization and performance. Let's explore the differences and when to use one over the other.
Serializable Interface:The Serializable interface is a marker interface that indicates that a class can be serialized (converted into a byte stream) and deserialized (reconstructed from a byte stream). When a class implements Serializable, the serialization and deserialization process is handled by the Java runtime, and you have limited control over the process.
package codeKatha;
import java.io.*;
public class SerializableExample implements Serializable {
private int data;
// Constructors, getters, setters, and other methods...
public static void main(String[] args) {
// Serialization
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"))) {
SerializableExample obj = new SerializableExample();
oos.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialization
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.ser"))) {
SerializableExample obj = (SerializableExample) ois.readObject();
// Use the deserialized object
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Externalizable Interface:
The Externalizable interface is also used for object serialization, but it provides more control and customization over the serialization and deserialization process. When a class implements Externalizable, you must implement the `writeExternal` and `readExternal` methods, which define how the object is serialized and deserialized.
package codeKatha;
import java.io.*;
public class ExternalizableExample implements Externalizable {
private int data;
// Constructors, getters, setters, and other methods...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// Custom serialization logic
out.writeInt(data);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Custom deserialization logic
data = in.readInt();
}
public static void main(String[] args) {
// Serialization
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"))) {
ExternalizableExample obj = new ExternalizableExample();
oos.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialization
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.ser"))) {
ExternalizableExample obj = (ExternalizableExample) ois.readObject();
// Use the deserialized object
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
When to Use Serializable vs. Externalizable:
- Use Serializable when you want a simple and straightforward way to serialize and deserialize objects with minimal effort. It's suitable for most cases and provides good performance for many scenarios.
- Use Externalizable when you need full control over the serialization and deserialization process. This is useful when you want to customize the format of the serialized data, omit certain fields, or perform encryption/decryption during serialization. However, keep in mind that Externalizable requires more code and can be less convenient than Serializable.
Performance Implications:
- Serializable can have better default performance in some cases because it relies on Java's built-in serialization mechanisms. However, it may serialize more data than you need, leading to larger serialized objects.
- Externalizable allows you to optimize serialization by selectively choosing what data to write and read. It can result in smaller and more efficient serialized objects. However, the customization overhead may offset these gains in simple cases.
Could you provide the differences between Heap and Stack Memory in the context of Java, and also provide insights into how these memory areas are used?
In Java, memory management plays a crucial role in determining how objects are stored and accessed during program execution. Heap and Stack Memory are two distinct regions where different types of data are managed. Understanding their differences is essential for efficient memory utilisation and managing object lifecycles.
Stack MemoryStack Memory is a region used for storing method calls, local variables, and references to objects. It operates in a Last-In-First-Out (LIFO) manner, resembling a stack of items. Each method call creates a new frame in the stack, containing variables specific to that method.
Stack Memory is relatively fast for allocation and deallocation because it follows a strict order. However, it has limited space and is typically used for small, short-lived data. Primitive data types and references to objects are often stored here.
Example: Using Stack Memory
package codeKatha;
public class StackMemoryExample {
public static void main(String[] args) {
int a = 5;
int b = 10;
int sum = addNumbers(a, b);
System.out.println("Sum: " + sum);
}
public static int addNumbers(int x, int y) {
int result = x + y;
return result;
}
}
In this example, the variables 'a', 'b', 'x', 'y', 'result', and 'sum' are stored in the Stack Memory. As the methods are called and return, their corresponding frames are pushed and popped from the stack.
Stack Memory
-----------------
[addNumbers Frame]
result = 15
y = 10
x = 5
[main Frame]
sum = 15
b = 10
a = 5
Heap Memory
Heap Memory is a region used for dynamic memory allocation, primarily for objects that have varying lifetimes. Unlike Stack Memory, Heap Memory doesn't have a strict order, and objects can be allocated and deallocated in any order.
Objects stored in the Heap are accessed through references stored in the Stack. Objects in Heap Memory can exist beyond the scope of a single method and can be shared among multiple methods or even different threads.
Example: Using Heap Memory
package codeKatha;
public class HeapMemoryExample {
public static void main(String[] args) {
Person person1 = new Person("Shanav", 25);
Person person2 = new Person("Advait", 30);
person1.sayHello();
person2.sayHello();
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + " and I'm " + age + " years old.");
}
}
In this example, the 'Person' objects are created in the Heap Memory using the 'new' keyword. The references to these objects ('person1' and 'person2') are stored in the Stack Memory. The objects can be accessed beyond the scope of the 'main' method, as shown in the 'sayHello' method.
Heap Memory
-----------------
|
[person1 Object] --> Person("Shanav", 25)
|
[person2 Object] --> Person("Advait", 30)
-----------------
In the Stack Memory example, you can see the method call frames and their associated local variables. In the Heap Memory example, the objects are allocated in the Heap, and the references to those objects are stored in the Stack.
Utilisation and ImplicationsUnderstanding the distinctions between Heap and Stack Memory is vital for efficient memory usage. Stack Memory is suited for small and short-lived data, while Heap Memory is used for dynamically allocated objects with varying lifetimes. Effective memory management ensures that objects are released when no longer needed, preventing memory leaks and improving program performance.
By carefully utilising Stack and Heap Memory, developers can optimise their programs for both memory efficiency and object lifecycle management.
Explain the differences between abstract classes and interfaces in Java. When should you use one over the other?
- Abstract Classes: Can have both abstract and non-abstract methods, can have instance variables, can have constructors, can have a partial implementation, and support inheritance. A class can extend only one abstract class.
- Interfaces: Can have only abstract methods (before Java 8), can have default and static methods (Java 8 onwards), cannot have instance variables, cannot have constructors, provide no implementation, and support multiple inheritance. A class can implement multiple interfaces.
- If you want to provide a partial implementation or share common functionality across related classes.
- If you want to use instance variables and constructors in your abstraction.
- If you want to enforce a specific inheritance hierarchy.
- If you want to provide a contract that multiple unrelated classes can implement.
- If you need a class to inherit from multiple abstractions.
- If you want to provide a common API for different implementations.
Explain the concept of method overloading and method overriding in Java. What are the rules for each?
- Methods must have the same name but different parameter lists.
- Methods can have different return types, access modifiers, and exception lists.
- Constructor overloading is also possible in Java.
- Methods must have the same name, return type, and parameter list as the superclass method.
- The access level of the overriding method cannot be more restrictive than the superclass method.
- If the superclass method throws checked exceptions, the overriding method can throw the same exceptions, subclasses of those exceptions, or no exception, but it cannot throw a new checked exception or a higher-level exception.
- The @Override annotation can be used to indicate that a method is intended to override a superclass method, helping the compiler catch errors.
Explain the concept of Java generics. What are some benefits of using generics, and what are some common use cases?
- Type safety: Generics help ensure type safety by checking the types of objects at compile time.This helps to catch type-related errors early and reduces the chances of runtime ClassCastException.
- Code reusability: Generics enable you to write generic classes, interfaces, and methods that can be reused with different types, reducing code duplication and increasing maintainability.
- Improved readability: Generics make the code more expressive and easier to read, as the types of objects being used are explicitly defined.
- Collections: One of the most common use cases for generics is in the Java Collections Framework. Generics allow you to create type-safe collections like List<String>, Set<Integer>, and Map<String, Integer>, ensuring that only the specified type of objects can be added or retrieved from the collection.
- Custom generic classes and interfaces: You can create your own generic classes and interfaces to handle multiple data types while maintaining type safety. For example, you could create a generic Pair class that can store two objects of different types:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// Getters and setters
}
public static <T extends Comparable<T>> T findMax(List<T> list) {
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
Explain the difference between final, finally, and finalize in Java.
- final variables: A final variable can be assigned a value only once, either at the time of declaration or within a constructor. Once assigned, its value cannot be changed. Final variables are often used to create constants.
- final methods: A final method cannot be overridden by a subclass, ensuring that the method's behavior remains consistent across the class hierarchy.
- final classes: A final class cannot be extended, preventing the creation of subclasses. This is useful for creating immutable classes or classes with sensitive behavior that should not be altered.
try {
// Code that might throw an exception
} catch (IOException e) {
// Code to handle the exception
} finally {
// Code that will always be executed, regardless of whether an exception was thrown or not
// For example: close file handles or database connections
}
@Override
protected void finalize() throws Throwable {
// Cleanup code, for example: release resources or close connections
// Note: It is not recommended to rely on finalize() for resource management
}
Explain the difference between the Comparable and Comparator interfaces in Java.
- A class must implement the Comparable<T> interface and define the compareTo() method.
- The compareTo() method should return a negative, zero, or positive integer, depending on whether the current object is less than, equal to, or greater than the object being compared.
public class Employee implements Comparable<Employee> {
private int id;
private String name;
// Constructor, getters, and setters
@Override
public int compareTo(Employee other) {
return Integer.compare(this.id, other.id);
}
}
- Create a separate class that implements the Comparator<T> interface and defines the compare() method.
- The compare() method should return a negative, zero, or positive integer, depending on whether the first object is less than, equal to, or greater than the second object.
- Use the custom Comparator class to sort a collection of objects using the Collections.sort() method or other sorting methods.
public class EmployeeNameComparator implements Comparator<Employee> {
@Override
public int compare(Employee e1, Employee e2) {
return e1.getName().compareTo(e2.getName());
}
}
// Sorting a list of Employee objects by name
Collections.sort(employeeList, new EmployeeNameComparator());
What are the key differences between ArrayList and LinkedList in Java?
- Underlying data structure: An ArrayList uses a dynamic array to store its elements.
- Access time: ArrayList provides fast random access (O(1)) to its elements due to its array-based structure.
- Insertion and deletion: Inserting or deleting elements in the middle of an ArrayList is slow (O(n)) because it requires shifting elements to maintain a contiguous array.
- Memory overhead: ArrayLists have a lower memory overhead compared to LinkedLists, as they do not require additional memory for pointers.
- Use cases: ArrayList is suitable for scenarios where frequent random access is needed and insertions/deletions are infrequent or mostly occur at the end of the list.
List<String> arrayList = new ArrayList<>();
- Underlying data structure: A LinkedList uses a doubly-linked list to store its elements.
- Access time: LinkedList provides slow random access (O(n)) to its elements, as it needs to traverse the list from the beginning or the end.
- Insertion and deletion: Inserting or deleting elements in the middle of a LinkedList is fast (O(1)) if the reference to the node is already known, as it only requires updating the pointers.
- Memory overhead: LinkedLists have a higher memory overhead compared to ArrayLists, as they require additional memory for pointers (next and previous) for each element.
- Use cases: LinkedList is suitable for scenarios where frequent insertions and deletions are needed, and random access is not a primary requirement.
List<String> linkedList = new LinkedList<>();
What is the purpose of the hashCode() and equals() methods in Java?
@Override
public int hashCode() {
// Calculate and return hash code
}
- Reflexive: x.equals(x) should return true for any non-null reference value x.
- Symmetric: x.equals(y) should return the same value as y.equals(x) for any non-null reference values x and y.
- Transitive: If x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should also return true for any non-null reference values x, y, and z.
- Consistent: Multiple invocations of x.equals(y) should consistently return the same value, provided that neither x nor y are modified between invocations.
- For any non-null reference value x, x.equals(null) should return false.
@Override
public boolean equals(Object obj) {
// Compare and return true if objects are equal, false otherwise
}
What are some common uses of Java Reflection API?
- Dynamic object creation: Reflection allows you to instantiate objects without knowing their class at compile time. This can be useful for creating objects based on user input or configuration files.
- Method invocation: With Reflection, you can invoke methods on objects without knowing the methods at compile time. This is helpful for implementing features like plugins or scripting languages that interact with Java code.
- Accessing private members: Reflection can be used to access private fields and methods of an object, which can be useful for testing or debugging purposes. However, using Reflection to break encapsulation in regular code is discouraged.
- Inspecting class metadata: Reflection allows you to retrieve information about classes, such as their fields, methods, constructors, and annotations. This can be used for generating documentation, implementing custom serialization, or validating code.
package codeKatha;
import java.lang.reflect.Field;
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void display() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
public class ReflectionExample {
public static void main(String[] args) {
// Create an instance of the Student class
Student student = new Student("Advait", 20);
// Use Reflection to access and modify private fields
Class<?> studentClass = student.getClass();
try {
// Access the 'name' field
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true); // Allow access to private field
String nameValue = (String) nameField.get(student);
System.out.println("Name Field: " + nameValue);
// Access the 'age' field
Field ageField = studentClass.getDeclaredField("age");
ageField.setAccessible(true);
int ageValue = (int) ageField.get(student);
System.out.println("Age Field: " + ageValue);
// Modify the 'name' field
nameField.set(student, "Shanav");
System.out.println("Updated Name Field: " + student.display());
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
In this example:
We define a Student class with private fields name and age.
In the ReflectionExample class, we create an instance of Student.
We use Java Reflection to access and modify the private fields of the Student class.
We get the class object for Student using getClass().
We access and modify the private fields by name using getDeclaredField() and setAccessible(true) to bypass access control checks.
We retrieve and print the values of the fields, both before and after modifying them.
Please note that using Reflection to access or modify private fields should be done with caution, as it can break encapsulation and lead to unexpected behavior. It's typically used in special cases where you need to interact with classes in a way that can't be achieved through regular means.
Define the role of a ClassLoader in Java and its significance within the runtime environment.
ClassLoader Role and Significance:A ClassLoader in Java is a crucial component responsible for dynamically loading classes during runtime. It plays a vital role in the Java Virtual Machine (JVM) by locating and loading class files from various sources like the file system, network, or other custom sources. The ClassLoader ensures that classes are available for execution as needed, contributing to Java's flexibility and extensibility.
ClassLoader Hierarchy:
Java uses a hierarchical ClassLoader system, comprising the following levels:
- Bootstrap ClassLoader: The top-level ClassLoader responsible for loading core Java classes provided by the JVM itself.
- Extension ClassLoader: Loads classes from the Java Standard Extension libraries.
- Application ClassLoader: Also known as the system ClassLoader, it loads classes from the application's classpath.
- Custom ClassLoaders: Developers can create custom ClassLoaders to load classes from non-standard sources.
ClassLoader Workflow:
When a class is needed during runtime, the ClassLoader follows a specific sequence to locate and load it:
- Bootstrap ClassLoader: Checks if the class is a core Java class provided by the JVM.
- Extension ClassLoader: Looks for the class in Java's standard extension libraries.
- Application ClassLoader: Searches for the class in the application's classpath.
- Custom ClassLoaders: If the class is not found in the above ClassLoaders, custom ClassLoaders are consulted based on the application's logic.
ClassLoader Example:
package codeKatha;
public class ClassLoaderExample {
public static void main(String[] args) {
// Get the class loader for a class
ClassLoader classLoader = String.class.getClassLoader();
// Print the class loader
System.out.println("ClassLoader for String: " + classLoader);
}
}
In this example, we obtain the ClassLoader for the String class, which is typically the Bootstrap ClassLoader as it's a core Java class.
ClassLoader Significance:
- ClassLoader enables dynamic loading of classes, facilitating features like reflection, plugins, and hot swapping.
- It supports isolation between classes loaded by different ClassLoaders, enhancing security and avoiding conflicts.
- ClassLoader hierarchies help manage classloading efficiently and promote code modularity.
ClassLoader is a fundamental component of the Java runtime environment, contributing to its adaptability, security, and extensibility.
What is the difference between Checked and Unchecked Exceptions in Java?
public void readFile(String fileName) throws IOException {
// Read file and handle IOException
}
public int divide(int a, int b) {
// Divide and potentially throw ArithmeticException if b is 0
}
What is the Singleton design pattern in Java, and how can it be implemented? Include a code example.
The Singleton design pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful when you want to restrict the creation of multiple objects of a particular class and ensure that there's a single instance shared across the entire application.
Implementation of Singleton Pattern:Here's a simple implementation of the Singleton pattern in Java:
package codeKatha;
public class Singleton {
// Private static instance variable to hold the single instance
private static Singleton instance;
// Private constructor to prevent external instantiation
private Singleton() {
}
// Public method to provide access to the single instance
public static Singleton getInstance() {
// Lazy initialization: create the instance only when needed
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// Other methods and properties of the Singleton class
}
In this example:
1.We have a private static instance variable instance that holds the single instance of the class.
2.The constructor Singleton is private, which means that it cannot be accessed from outside the class, preventing external instantiation.
3.The public static method getInstance is used to provide access to the single instance. It uses lazy initialization, meaning the instance is created only when getInstance is called for the first time.
Usage of Singleton Pattern:
You can use the Singleton pattern as follows:
package codeKatha;
public class SingletonExample {
public static void main(String[] args) {
// Get the singleton instance
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
// Both instances are the same
System.out.println(singleton1 == singleton2); // Output: true
}
}
In this usage example, we obtain the Singleton instance twice using getInstance, and we can see that both references (singleton1 and singleton2) point to the same instance. This ensures that there's only one instance of the Singleton class throughout the application.
The Singleton pattern is helpful when you want to control access to a shared resource or configuration, ensure that there's only one instance of a manager or controller, or implement a caching mechanism where you want a single cache instance.
What is the Java ClassLoader, and what are its primary functions? Include a code example.
try {
Class<?> myClass = Class.forName("com.example.MyClass");
Object myInstance = myClass.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
- Loading: Reads the binary data of a class from various sources, such as the file system, a JAR file, or a network location, and converts it into an instance of java.lang.Class.
- Linking: Verifies the correctness of the class, resolves symbolic references, and prepares the class for execution by allocating static fields and initializing static variables.
- Initialization: Executes the static initializer block of the class, if present, and assigns the initial values to the static fields.
What is the purpose of the "synchronized" keyword in Java, and when should it be used? Include a code example.
class BankAccount {
private double balance;
public synchronized void deposit(double amount) {
balance += amount;
}
public synchronized void withdraw(double amount) {
balance -= amount;
}
public synchronized double getBalance() {
return balance;
}
}
- You have a shared resource or a critical section of code, such as the BankAccount's balance, that can be accessed and modified by multiple threads concurrently.
- You need to ensure that only one thread can access the resource or execute the code block at a time to prevent data inconsistency or race conditions.
What is the difference between a shallow copy and a deep copy in Java, and when should you use each one? Include a code example.
Shallow Copy:A shallow copy creates a new object that is a copy of the original object, but it does not create new copies of the objects referenced by the original. Instead, it copies references to the same objects. As a result, changes to the objects inside the copy are reflected in the original and vice versa.
package codeKatha;
class Student {
String name;
Course course;
public Student(String name, Course course) {
this.name = name;
this.course = course;
}
}
class Course {
String name;
public Course(String name) {
this.name = name;
}
}
public class ShallowCopyExample {
public static void main(String[] args) {
Course course = new Course("Computer Science");
Student originalStudent = new Student("Advait", course);
Student shallowCopyStudent = new Student(originalStudent.name, originalStudent.course);
// Changes in course name affect both original and shallow copy
shallowCopyStudent.course.name = "Mathematics";
System.out.println(originalStudent.course.name); // Output: Mathematics
}
}
Deep Copy:
A deep copy creates a new object and also recursively creates new copies of the objects referenced by the original. This ensures that changes in the copied objects do not affect the original or vice versa.
package codeKatha;
public class DeepCopyExample {
public static void main(String[] args) {
Course course = new Course("Computer Science");
Student originalStudent = new Student("Advait", course);
Student deepCopyStudent = new Student(originalStudent.name, new Course(originalStudent.course.name));
// Changes in course name of deep copy don't affect the original
deepCopyStudent.course.name = "Mathematics";
System.out.println(originalStudent.course.name); // Output: Computer Science
}
}
Key Differences:
- Shallow Copy: Copies references, changes affect both copies.
- Deep Copy: Creates new objects, changes are isolated.
- You want to create a new object with the same values for its fields as the original object, and you don't need to protect the original object from changes made to its referenced objects.
- You want to create a completely independent copy of the original object, including creating new instances of any referenced objects, to prevent changes made to the referenced objects from affecting the copy.
Tell me about clone() function, give some example.
We need to write the number of codes for this deep copy/ Shallow copy. So to reduce this, In java, there is a method called clone(). The clone()
function in Java is used to create a copy of an object. It creates a new instance that is a duplicate of the original object. The clone()
method is provided by the Cloneable
interface, and it performs a shallow copy by default. However, for deep copying, you need to override the clone()
method to create new copies of referenced objects.
The default behavior of the clone()
method is shallow copying. It creates a new object with copies of the fields and references to the same objects that the original object references.
package codeKatha;
class Person implements Cloneable {
String name;
Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
public class CloneShallowExample {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("Delhi");
Person originalPerson = new Person("Advait", address);
Person clonedPerson = (Person) originalPerson.clone();
// Changing city in the cloned address affects the original
clonedPerson.address.city = "Muzaffarpur";
System.out.println(originalPerson.address.city); // Output: Muzaffarpur
}
}
Deep Copy using clone():
To achieve a deep copy using the clone()
method, you need to override the clone()
method and manually clone the referenced objects.
package codeKatha;
public class CloneDeepExample {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("Delhi");
Person originalPerson = new Person("Advait", address);
Person clonedPerson = (Person) originalPerson.clone();
clonedPerson.address = (Address) originalPerson.address.clone();
// Changing city in the cloned address doesn't affect the original
clonedPerson.address.city = "Muzaffarpur";
System.out.println(originalPerson.address.city); // Output: Delhi
}
}
How has Java evolved over the years, and what are some key features introduced in recent Java versions (Java 8 onwards)?
- Java 8: Introduces Lambdas, Functional Interfaces, Streams API, Optional class, and default/static methods in interfaces, along with a new Date and Time API.
- Java 9: Brings the Java Platform Module System (JPMS), JShell (REPL), factory methods for collections, Stream API enhancements, and private methods in interfaces.
- Java 10: Adds Local variable type inference (var), Application Class-Data Sharing (AppCDS), and garbage collector improvements.
- Java 11: Introduces the HTTP Client API, Epsilon garbage collector, new string methods, local variable syntax for lambda parameters, and Nest-based access control.
- Java 12: Offers Switch expressions (preview), Shenandoah garbage collector, and JVM Constants API.
- Java 13: Presents Text blocks (preview), Switch expressions enhancements, and ZGC garbage collector improvements.
- Java 14: Includes Pattern Matching for instanceof (preview), Records (preview), Text blocks enhancements, and makes Switch expressions standard.
- Java 15: Text blocks and Pattern Matching for instanceof become standard, and introduces Records (second preview).
- Java 16: Enhances Pattern Matching for instanceof, makes Records standard, introduces Foreign Function & Memory API (Incubator), and strengthens encapsulation.
- Java 17 (LTS): Features Sealed Classes, Pattern Matching for switch, standardizes the Foreign Function & Memory API, and continues to improve security and maintainability.
Comments
Post a Comment