Welcome to “Java Interview Questions,” your essential companion for preparing for Java interviews. This guide is designed to support candidates at every level of their Java developer interview journey. We’ve gathered a wide range of java interview questions that are frequently asked in Java interviews, covering everything from basic concepts to more advanced topics.
Our collection has covered also the basics of Java programming, such as syntax, data types, and control structures, in a manner that’s easy to understand. This foundational knowledge is also crucial for beginners as well as for experienced java developers.
As you go deeper into the article, the questions gradually increase in complexity, covering intermediate and advanced topics. This includes areas like object-oriented programming principles, exception handling, multithreading, and Java’s rich API libraries. For each question, we offer clear explanations and, where applicable, code examples to illustrate key points. This structure is designed to build your knowledge step by step, ensuring you’re prepared for the kinds of questions that interviewers love to ask.
This list of most important and mostly asked Java Interview Questions are designed to help both freshers who is looking to start their career as java developer and experienced Java developers seeking new opportunities. It’s your roadmap to navigating the interview process successfully and showing that you have what it takes to excel in a Java development role.
This collection of Java developer interview questions has been gathered from a variety of platforms and sources, ensuring a comprehensive overview of what you might face in the interview process. We’ve carefully chosen questions that are not only fundamental to understanding Java but also reflective of the types of questions asked by leading IT companies. Whether you’re aiming to join the top MNCs like Accenture, Cognizant, TCS, Wipro, LTI Mindtree, Infosys etc, these questions are tailored to give you an edge. By preparing with this guide, you’ll be engaging with the kind of material that has helped many candidates secure positions in these top MNCs, equipping you with the knowledge and confidence to excel in your interviews.
Java Basics
Check out our “Java Basics” section, perfect for anyone getting ready for Java interviews. This part of our website has a great list of Java interview questions. These questions start with easy topics like loops and arrays and go all the way to tougher topics like garbage collection and how to handle multiple tasks at once.
We’ve designed these questions to help both beginners and experienced programmers understand and remember important Java ideas. They are great for practicing, so you feel confident and ready for your interviews. Whether you’re brushing up on old skills or learning new ones, our Java Basics section is here to help you do well in your Java interviews.
1. Describe the process of compiling Java source code into bytecode.
Ans: Compiling the Java source code into bytecode is the important part of the Java Program executions. Bytecode is an intermediate representation of the code that is platform-independent and can be executed on any Java Virtual Machine (JVM). This Bytecode makes Java Program platform independent.
Writing Java Source Code: Our first step is writing Java source code using a text editor or Integrated Development Environment (IDE). Save the Java source code files with a .java extension.
Compilation: Once the source code is written and saved in .java file. Now next turn is to compile it. Compilation is the process of translating human-readable Java source code into platform-independent bytecode. javac compiler is used for this translation. It is included with the Java Development Kit (JDK).
Command to compile Java File
javac YourJavaFile.javaAbove code will generate a bytecode file with a .class extension. If your source file is named MyClass.java, the bytecode file will be named MyClass.class.
2. What is bytecode, and how is it different from machine code?
Bytecode and Machine code, both are the forms of low-level representations of code. They both have different purposes and are used in different contexts.
Bytecode
Bytecode is an intermediate representation of code that is generated by a compiler from the human readable source code. It is not directly executed by a physical computer’s CPU. It is designed to be executed by a virtual machine. In Java Programing language there is Java Virtual Machine (JVM) to execute bytecode. And in .NET languages there is Common Language Runtime (CLR) to execute byte code.
Machine Code
Machine code is a binary representation of instructions that can be directly executed by a computer’s central processing unit (CPU). It consists of sequences of binary numbers (1s and 0s) that correspond to specific CPU instructions.
3. Describe the structure of a Java class file.
Ans: A Java class file is a binary file that contains the compiled bytecode representation of a Java class. It adheres to a specific structure defined by the Java Virtual Machine (JVM) specification. The structure of a Java class file is organized into various sections and data structures.
Key components and structure of a typical Java class file:
- Magic Number: The class file begins with a 4-byte “magic number” that identifies it as a valid Java class file. The magic number is always
0xCAFEBABEin hexadecimal. - Minor and Major Version: Following the magic number are two 2-byte fields that represent the minor and major versions of the class file format.
- Constant Pool: The constant pool is a table of constants used within the class. It includes literal values, symbolic references to classes, fields, and methods, and other constant data.
- Access Flags: A 2-byte field that specifies access modifiers and other flags associated with the class, such as whether it is public, final, abstract, etc.
- This Class and Super Class: These two fields are 2-byte indices into the constant pool, specifying the fully qualified names of the current class and its superclass.
- Interfaces: A count of the number of interfaces the class implements, followed by an array of 2-byte indices pointing to the constant pool entries representing those interfaces.
and some more are Fields, Methods, Attributes and Class File Attributes.
4. How does the Java Virtual Machine (JVM) execute Java bytecode?
Ans: The Java Virtual Machine (JVM) is responsible for executing Java bytecode, which is the compiled form of Java source code. Here’s a high-level overview of how the JVM executes Java bytecode:
Class Loading:
Here JVM first loads the necessary classes into memory. Class loading is typically performed as needed, so classes are loaded when they are first referenced in the code.
The class loading process involves locating the bytecode for a class (usually from .class files or JAR files), reading the bytecode, and creating a representation of the class in memory. This representation includes the class’s methods, fields, and other metadata.
Bytecode Verification:
After a class is loaded, the JVM performs bytecode verification to ensure that the bytecode is valid, safe, and adheres to the language’s rules.
Execution:
Once a class is loaded and verified, the JVM is ready to execute its bytecode.
Two commonly used methods to execute bytecode are interpretation and Just-In-Time (JIT) compilation:
Interpretation: In the interpretation mode, the JVM reads each bytecode instruction and executes it step by step. This is a straightforward but relatively slower approach.
Just-In-Time (JIT) Compilation: In JIT compilation, the JVM translates bytecode into native machine code for the host platform just before executing it. The generated native code is then executed directly by the CPU, which is significantly faster than interpretation.
5. How does the JVM provide platform independence for Java applications?
JVM provide platform independence for Java applications by providing bytecode execution independently.
Java source code is first compiled into bytecode, which is a platform-independent representation of the code. Bytecode consists of instructions that are not tied to any specific hardware or operating system. The JVM is responsible for interpreting or compiling this bytecode into native machine code at runtime. And this makes it executable on any platform that has a compatible JVM.
6. What is the purpose of the classpath, and how does it affect class loading?
Classpath is a parameter in the Java Virtual Machine or the Java compiler that specifies the location of user-defined classes and packages. This is where the Java Virtual Machine (JVM) looks for classes and resources when loading them at runtime.
It specifies directories and JAR (Java Archive) files where the JVM should search for class files and resources (such as configuration files, images, and property files) required by a Java application.
7. What is Just-In-Time (JIT) compilation?
Just-In-Time (JIT) compilation is a technique to improve the execution performance of programs. It dynamically compiling and optimizing code during execution. It combines the advantages of both interpretation and native code execution while adapting to the specific runtime environment and usage patterns.
In JIT compilation, the JVM translates bytecode into native machine code for the host platform just before executing it. The generated native code is then executed directly by the CPU, which is significantly faster than interpretation.
8. Explain the roles of the Java stack, heap, and method area in the runtime environment.
In the Java runtime environment, the Java stack, heap, and method area play distinct roles in managing memory and executing Java applications.
Java Stack
The Java stack is responsible for managing and keep tracking the execution of Java methods. It keeps track of method call frames, which contain information about a method’s local variables and its execution context.
When a method is invoked, a new frame is pushed onto the stack, storing the method’s local variables and other context information. When the method completes, its frame is popped from the stack.
Java Heap
Java heap memory, often referred to simply as the “heap,” is a region of memory within the Java Virtual Machine (JVM) that is used for dynamic memory allocation. It is the area where Java objects are created and managed during the execution of a Java program.
It is responsible for object creation, garbage collection, and memory management. Java heap memory area is basically used for dynamic memory allocation, specifically for objects that have a longer lifespan than local variables on the stack.
Method Area
The method area is used to store class-level information, including class metadata, bytecode, and static fields. It is shared among all threads in the JVM and contains data related to classes, methods, and other class-level structures.
Class metadata, such as class names, methods, and fields, is stored in the method area. This information is needed for class loading, verification, and execution.
9. Explain the roles of the class loader, bytecode verifier, and execution engine in the JVM.
In the Java Virtual Machine (JVM), several components work together to load, verify, and execute Java bytecode. Among these components, the class loader, bytecode verifier, and execution engine play crucial roles in the execution of Java programs.
Class Loader
The class loader is responsible for loading classes into the JVM at runtime. It takes care of locating class files, reading their bytecode, and creating the necessary data structures to represent these classes in memory.
The class loading process typically involves three phases: loading, linking, and initialization.
Bytecode Verifier
The bytecode verifier is a crucial security component in the JVM. Its primary role is to verify that the loaded bytecode adheres to the Java language’s type safety rules and does not violate memory constraints.
It ensures that bytecode does not access memory outside its allocated space, preventing buffer overflows and other security vulnerabilities.
Execution Engine
The execution engine is responsible for executing the verified bytecode. It interprets or compiles bytecode into native machine code for execution by the CPU. Just-In-Time(JIT) compilation is its part.
10. Difference between Encapsulation and Data Hiding.
In Java, encapsulation and data hiding are closely related concepts, but they have distinct meanings and purposes.
| Aspect | Encapsulation | Data Hiding |
|---|---|---|
| Definition | Bundling data (attributes) and methods into a single unit (class) to hide internal details from the outside world. | The practice of hiding the internal state of an object by declaring attributes as private and providing controlled access through methods (getters and setters). |
| Access Control | Involves specifying access modifiers (private, protected, public, etc.) for class members (fields and methods) to control their visibility. | Typically involves marking attributes as private and providing public getter and setter methods to access and modify the attributes. |
11. What are generics?
Generics in programming languages, including Java, are a powerful feature. It allows us to write code that can work with different data types in a type-safe and reusable manner. The primary purpose of generics is to enable you to create classes, interfaces, and methods that operate on generic types. It ensure compile-time type safety.
Example:
public class Box<T> {
private T contents;
public Box(T contents) {
this.contents = contents;
}
public T getContents() {
return contents;
}
public void setContents(T contents) {
this.contents = contents;
}
}Generics are widely used in Java for collections (e.g., ArrayList<T>), data structures, and libraries to provide flexibility, type safety, and code reusability.
12. How many modifiers in Java?
There are a total of 12 access modifiers in Java. It basically control the visibility and accessibility of classes, methods, fields, and other members within a Java program.
These modifiers determine which parts of a Java class or interface can be accessed from other classes and packages.
List of modifiers are
1. Public : Members with the public modifier are accessible from any class or package.
2. Private: Members with the private modifier are only accessible within the same class.
3. Protected: Members with the protected modifier are accessible within the same package or different package by subclasses. It is not accessibly outside the packages if there is no subclass.
4. Default (No Modifier): Members with no explicit access modifier (i.e., no public, private, or protected modifier) are also known as package-private or default. They are accessible within the same package but not from outside the package.
13. Which is better way to declare a string?
In Java there are two ways to declare a String.
1). Using String Literal
String str= "Hello, World!";This is Simple and concise. For same string, it share the references of heap memory rather than creating the new memory. It saves memory and improve performance.
Use string literals for constant strings that won’t change during the program’s execution.
2).Using the new Keyword
String str = new String("Hello, World!");Allows for dynamic creation of strings. it creates a new string object on the heap for each call, which can be less memory-efficient and slower compared to string literals.
Used it when you specifically want to create a new string object or when dealing with strings obtained from external sources.
14. Generics are compile time or runtime?
Its compile time.
15. Difference in static and instance methods.
In java, methods are categorized into two main types: static methods and instance methods.
Static Methods:
- It is declared using the
statickeyword. - Example:
public static void doSomething() { ... }. - Static methods are called with the class name, not on instances of the class.
- Example:
ClassName.doSomething(); - Static methods are often used for utility functions that are not tied to a specific instance but are related to the class as a whole.
- Examples include factory methods, helper functions, and mathematical operations.
public class MathUtils {
// Static method to calculate the factorial of a number
public static int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
}
public class Main {
public static void main(String[] args) {
int num = 5;
// Calling a static method directly on the class
int factorialResult = MathUtils.factorial(num);
System.out.println("Factorial of " + num + " is " + factorialResult);
}
}
Instance Methods:
- Instance methods are declared without the
statickeyword. - Example:
public void doSomething() { ... } - Instance methods are called on instances (objects) of the class.
- Example:
myObject.doSomething(); - Instance methods can access instance-specific data, including instance fields and other instance methods.
- They have access to
this, which refers to the current instance of the class.
public class Person {
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Instance method to greet
public void greet() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
}
public class Main {
public static void main(String[] args) {
// Creating instances of the Person class
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Bob", 25);
// Calling instance methods on the objects
person1.greet();
person2.greet();
}
}
16. How many types of memories are there in Java?
There are multiple types of memory in java and each have their own uses.
Here we are going to see two types
1). Heap Memory
2). Stack Memory
Heap Memory
- Heap memory is the region where objects are allocated and deallocated during the runtime of a Java program.
- Specifically for objects that have a longer lifespan than local variables on the stack.
- It is the primary storage area for objects created using the
newkeyword and includes objects, arrays, and their instance variables. - The Java Garbage Collector manages heap memory by reclaiming memory from objects that are no longer reachable or in use.
Stack Memory
- Stack memory is used for method execution and storing local variables, method parameters, and call stack frames.
- Local variables are short-lived and have a limited scope, as they exist only within the context of a single method call.
- Each thread in java has their own stack memory.
- The stack operates on a last-in, first-out (LIFO) basis, making it efficient for managing method calls and local data.
17. How to do a Boolean algebra evaluation in Java?
In Java, Boolean algebra evaluations can be done using logical operators and conditional statements. Boolean algebra involves operations such as AND, OR, NOT, and XOR on Boolean values (true or false).
18. Working of Serializable.
In Java, Serialization is a mechanism to convert objects into a bytes stream that can be easily stored to a file, transmitted over a network, or persisted in a database. This process allows you to save the state of an object and recreate it later, possibly on a different JVM (Java Virtual Machine).
Serializable interface works in Java
Serializable interface implementation
import java.io.Serializable;
public class MyClass implements Serializable {
// Class members and methods
}Serialization Process
Create an instance of the ObjectOutputStream class, passing it an output stream (e.g., a FileOutputStream or a network socket stream).
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("data.txt"));Use the writeObject() method of the ObjectOutputStream to write the object to the output stream.
MyClass myObject = new MyClass();
outputStream.writeObject(myObject);19. Static vs initialized block.
Static Block:
- Static blocks are used to initialize static members (static fields or static methods) of a class.
- They are executed only once when the class is loaded into memory. It happens before any objects of the class are created.
static {
// Initialization code for static members
}Instance Initialization Block:
- Instance initialization blocks are used to initialize instance members (instance fields) of a class.
- They are executed every time an object of the class is created, before the constructor(s) of the class are invoked.
{
// Initialization code for instance members
}Example to demonstrate Static block and instance block
public class StaticInstanceExample{
static {
System.out.println("Static block");
}
{
System.out.println("Instance block");
}
public StaticInstanceExample() {
System.out.println("Constructor executed");
}
public static void main(String[] args) {
StaticInstanceExample obj1 = new StaticInstanceExample();
StaticInstanceExample obj2 = new StaticInstanceExample();
}
}
Output
Static block Instance block Constructor executed Instance block Constructor executed
20. Is JDK 8 more performance tuned than earlier versions ?
Yes, Java Development Kit (JDK) 8 introduced several performance improvements and optimizations over earlier versions of Java. And this improvements makes it a more performance-tuned release.
Some improvements to notice are Lambda Expressions, Streams API, PermGen Removal, Method References, Improved Garbage Collection, Improved Date and Time API, Parallel and Concurrent APIs etc.
21. Fork join improvements as compared to jdk 7.
Java 7 introduced the Fork/Join Framework, which is a framework for parallelizing computations in Java applications. The primary goal of the Fork/Join Framework is to provide a simple and efficient way to divide a task into smaller subtasks, execute them in parallel, and then combine their results. In Java 7, this framework was considered a significant step forward for parallelism in Java.
22. What are different types of class loaders?
In Java, class loaders are responsible for loading classes and resources into memory when they are needed by a Java program.
Different types of class loaders are:
- Bootstrap Class Loader
- Extension Class Loader
- System Class Loader
- Custom Class Loaders
- Thread Context Class Loader
- Parallel and Concurrent Class Loaders
23. Composition and Aggregation
In Java, Composition and aggregation are two fundamental concepts. It describes the relationships between objects in object-oriented programming. These relationships help define the structure and behavior of a software system.
Composition
Composition is a strong association between two or more classes, where one class (the composite) contains or is composed of other objects (the components) as its parts.
In composition, the lifetime of the contained objects is controlled by the composite object. When the composite object is destroyed, its parts are also destroyed.
An example of composition is a Car class composed of Engine, Wheels, and Seats objects. When the Car object is created, it creates and owns its parts.
Aggregation
Aggregation is a weaker association between two or more classes, where one class (the whole) is associated with other objects (the parts), but the parts can exist independently of the whole.
In aggregation, the lifetime of the contained objects is not controlled by the whole, they can exist before or after the whole object.
An example of a Library class and Book objects. The Library class is associated with Book objects, but books can exist independently.
24. Marker Interface In Java.
In Java, marker interface is a interface without any methods and any fields. It is used to indicate certain characteristics or behaviors of a class that implements it. Marker interfaces are typically used to add metadata or to provide some special instructions to the compiler or runtime environment. They serve as a way to “mark” or “tag” classes, indicating that they possess certain qualities or should be treated in a particular way.
Some Marker Interfaces are:
1). Serializable Interface
2). Cloneable Interface
3). Remote Interface (for RMI)
4). Custom Marker Interfaces
25. What is singleton design pattern?
Singleton Design Pattern ensure that a class can have only one instance and that instance is shared by all parts of the code that need to access it. To prevent external instantiation, the Singleton class typically has a private constructor, making it impossible to create instances from outside the class.
Example
public class Singleton{
private static volatile Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}Singletons are commonly used in scenarios where there must be exactly one instance of a class to coordinate actions across the system, control access to a shared resource, or maintain a single point of control, such as:
- Database connections.
- Configuration management.
- Logging services.
- Caching mechanisms.
- Thread pools.
- Hardware access.
26. What is Builder Design pattern?
The Builder design pattern is a creational pattern used to construct a complex object step by step. It’s particularly useful when an object needs to be created with many possible configurations, some of which might be optional.
The Builder pattern encapsulates the construction of an object and allows it to be constructed in steps. This pattern separates the construction of a complex object from its representation, making it possible to use the same construction process to create different representations.
Example
Let’s illustrate the Builder pattern with a simple example of building a User object with various attributes.
Step 1: Construct the User Class
public class User {
private final String firstName; // required
private final String lastName; // required
private final int age; // optional
private final String phone; // optional
public User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
}
// Getters here
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(this);
}
}
}Step 2: Construct the Object
User user = new User.UserBuilder("John", "Doe")
.age(30)
.phone("1234567890")
.build();27. Can we override constructor?
No
28. Why java is not 100% object oriented programming language?
Java is often referred to as an object-oriented programming (OOP) language due to its emphasis on encapsulating data and behavior into objects and its use of core OOP principles such as inheritance, polymorphism, and abstraction. Here are few reasons that java is not 100% object oriented programming language:
- Java includes eight primitive data types (
boolean,byte,char,short,int,long,float,double) that are not objects. These primitive types are used for performance reasons, as they have a lower overhead than their object counterparts. While Java provides wrapper classes (likeIntegerforint,Floatforfloat, etc.) that encapsulate primitives in objects, the existence and use of primitives themselves are seen as a deviation from pure object-oriented design where everything should be an object. - Java allows the use of static methods and variables that can be called without creating an instance of a class. This means that static methods and variables are not associated with any object instance but rather with the class itself. In a purely object-oriented language, all methods would need to be accessed through instances of objects, and all data would belong to objects.
29. Why Functional Programming introduced in Java 8?
The introduction of functional programming features in Java 8 marked a significant evolution of the language, aimed at making Java more expressive, efficient, and capable of handling modern computing challenges.
One of the primary motivations for introducing functional programming in Java 8 was to simplify the development of parallel and asynchronous programs. With the increasing importance of multicore processors, there was a need for a programming model that could more easily leverage these hardware capabilities. Functional programming, with its emphasis on immutability and stateless operations, fits well with parallel computing. Features like Streams API allow for more readable and concise code that can be parallelized with minimal effort.
30. Tell me about PermGen and Metaspace.
In Java, both PermGen (Permanent Generation) and Metaspace deal with the storage of class metadata in the Java Virtual Machine (JVM). However, they are parts of different versions of Java, with Metaspace replacing PermGen in Java 8 due to various limitations and problems associated with PermGen.
PermGen (Permanent Generation)
PermGen was a specialized heap space separate from the main memory heap where Java stores its objects. Introduced in earlier versions of Java, it specifically held metadata describing user classes (classes that are not part of the Java language itself), such as class structures, method data, field data, and other information about classes and methods.
Key Characteristics and Challenges of PermGen:
- Fixed Size: PermGen had a fixed maximum size which had to be specified using JVM options at startup. If the space was exhausted, it caused
java.lang.OutOfMemoryError: PermGen spaceerrors. - Non-Resizable: Unlike the regular heap, the size of PermGen could not dynamically adjust at runtime based on the application’s demands.
- Garbage Collection: Although garbage collection did occur in PermGen, it was often not as effective because class metadata is generally long-lived, reducing the effectiveness of cleanup operations.
These characteristics often led to issues in environments with dynamic class loading and unloading, such as web servers and application servers, where applications are frequently redeployed without restarting the JVM.
Metaspace
Introduced in Java 8, Metaspace replaced PermGen. Metaspace addresses many of the limitations of PermGen by using native memory (the memory available to the JVM from the underlying operating system) to store class metadata.
Key Features of Metaspace:
- Dynamic Resizing: Unlike PermGen, Metaspace can dynamically resize depending on the application’s demands, reducing the likelihood of
OutOfMemoryErrorrelated to class metadata storage. - Native Memory Usage: Metaspace uses native memory, which generally allows for a larger amount of memory than what was typically allocated to PermGen. The size of Metaspace is only limited by the amount of native memory available to the Java process.
- Configurable Size Limits: While Metaspace can grow dynamically, developers can still control its growth using JVM options like
-XX:MetaspaceSizefor the initial size and-XX:MaxMetaspaceSizefor the maximum size, the latter of which can be left unbounded to allow Metaspace to grow as needed.
Benefits of Metaspace over PermGen:
- Scalability: The use of native memory allows Metaspace to scale more smoothly as the demand for class metadata storage increases, without the strict bounds that PermGen imposed.
- Fewer
OutOfMemoryErrors: The dynamic resizing capability helps to avoid out-of-memory errors under normal circumstances. - Improved Performance: Allocating and deallocating native memory can be faster than managing the same operations within a bounded space like PermGen.
Exception Handling
This part of our website offers a detailed list of questions about handling exceptions in Java, essential for ensuring programs run smoothly even when unexpected errors occur. The questions range from basic concepts, like try-catch blocks, to more advanced topics such as custom exceptions and error propagation.
Designed to aid both new learners and seasoned developers, this section enhances your ability to solve problems and manage errors effectively in Java. Perfect for review or learning new skills, our “Exception Handling” section equips you with the knowledge to handle interview questions confidently.
1. Can the multiple catch blocks exists? What can be their order?
Yes, multiple catch blocks can exist in a Java try-catch statement to handle different types of exceptions. The catch blocks are evaluated in top-down order. The first catch block that matches the type of the thrown exception (or a subtype) will be executed. This means that the order of catch blocks matters, and you should place more specific catch blocks before more general ones.
Or you can say first we keep the child class of exception then parent class.
Here’s an example:
try {
int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
System.out.println("ArithmeticException occurred.");
} catch (NullPointerException e) {
System.out.println("NullPointerException occurred.");
} catch (Exception e) {
System.out.println("General Exception occurred.");
}2. What is Exception? How you can create custom exceptions?
Ans: An exception is an abnormal and unexpected event that occurs during the execution of a program. Whenever exception occurs, It disrupts its normal flow. Exceptions handling in programing language is used to handle exception and protect it from the abnormal termination.
Custom Exception Creation
Custom exceptions can be created by extending either the Exception class for checked exceptions, which must be either caught or declared to be thrown, or the RuntimeException class for unchecked exceptions, which do not need to be explicitly handled.
Steps to Create a Custom Exception
- Define a New Exception Class: This class should extend either
ExceptionorRuntimeException, depending on whether you want it to be a checked or unchecked exception. - Add Constructors: Typically, you would add at least two constructors: one default constructor and one that takes a string message as an argument, which passes this message to the superclass constructor. You can also add a constructor that handles cause or one that handles both message and cause.
Example
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
public class CustomExceptionExample {
public static void main(String[] args) {
try {
// Simulate an error condition
int result = divide(10, 0);
System.out.println("Result: " + result);
} catch (CustomException ex) {
// Handle the custom exception
System.err.println("Custom Exception: " + ex.getMessage());
}
}
public static int divide(int numerator, int denominator) throws CustomException {
if (denominator == 0) {
// Throw the custom exception when division by zero occurs
throw new CustomException("Cannot divide by zero");
}
return numerator / denominator;
}
}
3. Can try exist individually?
Ans: No, a try block doesn’t exist independently. It usually needs at least one catch or finally block to handle exceptions or perform cleanup operations. The try block, along with catch and finally blocks, forms a structured exception handling construct.
Example:
public class Main
{
public static void main(String[] args) {
try{
int result = divide(10, 0);
System.out.println("Result: " + result);
}
}
}Output
Main.java:12: error: 'try' without 'catch', 'finally' or resource declarations
try{
^
1 error
4. try with resource?
Ans: “Try with resources” is a feature introduced in Java, specifically in Java 7, to simplify resource management, such as handling files, network connections, or other resources that need to be opened and closed properly. It helps ensure that resources are automatically closed when they are no longer needed, reducing the risk of resource leaks and making code cleaner and more reliable.
In Java, you can use the try-with-resources statement to automatically close resources that are declared within the parentheses of the try block. Resources that can be used with try-with-resources must implement the AutoCloseable interface, which includes the close() method. The try-with-resources statement ensures that the close() method is called on the resource(s) when the try block is exited, either normally or due to an exception.
Here’s the basic syntax of try-with-resources:
try (ResourceType resource1 = new ResourceType1();
ResourceType resource2 = new ResourceType2()) {
// Code that uses the resources
} catch (Exception e) {
// Exception handling
}Here’s an example of using try-with-resources to read data from a file in Java:
try (FileReader reader = new FileReader("example.txt")) {
int data;
while ((data = reader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}In this example:
FileReaderis a resource that implementsAutoCloseable.- The
tryblock declares and initializes theFileReaderresource within the parentheses. - The code reads data from the file within the
tryblock. - When the
tryblock is exited (either normally or due to an exception), theclose()method of theFileReaderis automatically called to close the file, ensuring proper resource management.
try-with-resources simplifies the code by removing the need for explicit finally blocks to close resources and by automatically handling exceptions related to resource closure. It’s considered a best practice for managing resources in Java.
5. Is Try catch within try block possible?
Yes, it is possible to have a try-catch block within another try block. This is often referred to as nested try-catch blocks.
Nested try-catch blocks can be useful when you want to handle exceptions at different levels of your code. The outer catch block can handle exceptions that are not caught by inner catch blocks, providing a way to manage exceptions at various scopes in your code.
6. Difference between Final, finally, finalize.
final:
finalis a keyword in Java that can be applied to classes, methods, and variables.- When applied to a class, it makes the class uninheritable, meaning it cannot be extended by other classes.
- When applied to a method, it prevents the method from being overridden by subclasses.
- When applied to a variable, it makes the variable a constant, and its value cannot be changed once assigned.
public class FinalExample {
public static void main(String[] args) {
final int x = 20; // A final variable
// x = 40; // This would result in a compilation error because x is final
System.out.println("Value of x: " + x);
}
}finally:
finallyis a keyword used in Java to define a block of code that is always executed, whether an exception is thrown or not, after thetryortry-catchblock.- It is commonly used for cleanup tasks or to ensure certain code is executed even in the presence of exceptions.
public class FinallyExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // This will throw an exception
} catch (ArithmeticException e) {
System.out.println("An exception occurred: " + e.getMessage());
} finally {
System.out.println("Finally block always executes.");
}
}
}finalize:
finalizeis a method defined in thejava.lang.Objectclass.- It’s called by the Java Virtual Machine (JVM) when an object is about to be garbage collected (i.e., when there are no more references to the object).
- You can override this method in your classes to perform cleanup or resource release operations before the object is destroyed.
- In Summary,
finalizeis a method called by the JVM during garbage collection and can be overridden for custom cleanup operations, but it’s not commonly used for resource management in modern Java.
class MyResource {
public void open() {
System.out.println("Resource opened.");
}
public void close() {
System.out.println("Resource closed.");
}
@Override
protected void finalize() throws Throwable {
close(); // Perform cleanup when the object is garbage collected
}
}
public class FinalizeExample {
public static void main(String[] args) {
MyResource resource = new MyResource();
resource.open();
// resource = null; // Uncomment this line to make the object eligible for garbage collection
System.gc(); // Suggest the JVM to run garbage collection
}
}
7. Exception handling enhancement in Java 8?
Java 8’s introduction of lambda expressions and the Stream API indirectly affected how developers might deal with exceptions in certain contexts, particularly in functional-style programming constructs.
Catch Exceptions Within the Lambda: Directly handling exceptions inside the lambda expression.
Runnable r = () -> {
try {
throw new IOException();
} catch (IOException e) {
e.printStackTrace();
}
};8. Difference between checked and unchecked exceptions?
Java exceptions are broadly classified into two main types: checked and unchecked exceptions.
Checked Exceptions
- Explanation: Checked exceptions are exceptions that must be either handled within a try-catch block or declared in the method signature using the
throwskeyword. They are checked at compile-time, which means if a method could throw a checked exception, the programmer is forced to handle it. If not handled or declared, the program will not compile. - Examples: IOException, SQLException. These are typically external to the program and depend on the runtime environment.
Unchecked Exceptions:
- Explanation: Unchecked exceptions are not checked at compile time. It means if your code throws an unchecked exception, the compiler does not force you to handle it. These exceptions are checked at runtime.
- Examples: NullPointerException, ArrayIndexOutOfBoundsException. These are usually the result of bad programming.
9. What is the throw keyword?
The throw keyword in Java is used to explicitly throw an exception from a method or any block of code. When you use the throw keyword, you are essentially creating an instance of an exception and passing it to the runtime system to handle it.
Purpose: The main purpose of using throw is to let you handle errors or any other exceptional conditions in your code by throwing an exception when a specific error condition occurs.
How It Works:
- Throwing an Exception: You can throw either a new instance of an exception or an existing instance that has been caught in a catch block.
- Handling an Exception: Once an exception is thrown, it must be caught and handled by a catch block. If it is not caught within the current method, it propagates up the call stack to previous methods until it finds an appropriate handler. If no handler is found, the program terminates.
Example Usage:
Here is a simple example that shows how to use the throw keyword:
public class Main {
public static void checkAge(int age) {
if (age < 18) {
// Throw an ArithmeticException if the age is below 18
throw new ArithmeticException("Access denied - You must be at least 18 years old.");
} else {
System.out.println("Access granted - You are old enough!");
}
}
public static void main(String[] args) {
try {
checkAge(15); // This will throw an exception
} catch (ArithmeticException e) {
System.out.println("Exception caught: " + e.getMessage());
}
System.out.println("Program continues after the catch block.");
}
}In this example:
- The
checkAgemethod throws anArithmeticExceptionif theageparameter is less than 18. - The exception is thrown using the
throwkeyword followed by thenewkeyword to create a new instance ofArithmeticException. - The
mainmethod callscheckAgeand catches the exception in a try-catch block. The program handles the exception by printing a message and then continues.
10. What is the throws keyword? To Do
The throws keyword in Java is used in the method signature to indicate that the method might throw one or more exceptions during its execution. It effectively communicates to the callers of the method that they need to handle these exceptions.
Purpose: The purpose of using throws is to declare that a method will not handle certain exceptions itself, instead passing the responsibility to handle these exceptions up to the method that calls it. This is particularly useful when you want to maintain clean code separation and push error handling responsibilities to higher layers of an application.
How It Works:
- Declaring Exceptions: When a method could throw checked exceptions (exceptions that the compiler checks for during the compilation time, such as IOException), these exceptions must be declared using
throws. - Propagation: If a method that throws checked exceptions is called, the calling method must either handle these exceptions with a try-catch block or declare that it throws them as well, propagating them further up the call stack.
Example
public class DivisionCalculator {
// Method to divide two numbers, declaring that it can throw ArithmeticException
public static double divide(int numerator, int denominator) throws ArithmeticException {
if (denominator == 0) {
// Explicitly throw ArithmeticException if denominator is zero
throw new ArithmeticException("Cannot divide by zero.");
}
return (double) numerator / denominator;
}
public static void main(String[] args) {
try {
// Calling divide method. It's necessary to handle the declared exception.
double result = divide(10, 0);
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
// Catching the ArithmeticException
System.out.println("Caught an exception: " + e.getMessage());
}
}
}Explanation:
- Method Definition: The
dividemethod is defined to take two integers as input: a numerator and a denominator. It returns adoubletype result of their division. - Exception Handling: It declares that it can throw an
ArithmeticExceptionusing thethrowskeyword. This is because dividing by zero is an illegal operation in arithmetic, which could lead to an exception. - Throwing an Exception: Inside the method, there is a check to see if the denominator is zero. If it is, the method throws an
ArithmeticExceptionwith a message “Cannot divide by zero.” - Method Call: In the
mainmethod,divideis called. Sincedividecan throw anArithmeticException, this call is wrapped in a try-catch block to handle the potential exception. - Error Handling: If an
ArithmeticExceptionoccurs, it is caught in the catch block, and an error message is printed.
11. How do you handle exceptions in a method that returns a value?
Handling exceptions in a method that returns a value requires careful consideration to ensure that all possible execution paths are covered and that the method can handle exceptional circumstances gracefully. Here are the common strategies you can use:
1. Return a Special Value
One way to handle exceptions in methods that must return a value is to designate a special return value to indicate an error condition.
Example
public int divide(int numerator, int denominator) {
try {
return numerator / denominator;
} catch (ArithmeticException e) {
// Returning a special value to indicate an error
return Integer.MIN_VALUE;
}
}Explanation: In this example, if an ArithmeticException occurs (e.g., division by zero), the method returns Integer.MIN_VALUE to indicate something went wrong. The caller must understand that this value signifies an error.
2. Throw a Custom Exception
If an error condition should not be masked by a special or default value, you might want to throw a custom exception that can be handled further up the call stack.
Example
public double divide(int numerator, int denominator) throws InvalidDivisionException {
if (denominator == 0) {
throw new InvalidDivisionException("Can't divide by zero.");
}
return (double) numerator / denominator;
}Explanation: Instead of handling the exception within the method, this approach throws a custom exception. The method signature must declare this exception (unless it is a runtime exception), and the caller is responsible for handling it.
12. Give the list of some compile time exceptions.
Some Common Compile-Time (Checked) Exceptions
- ClassNotFoundException
- IOException
- FileNotFoundException
- SQLException
- NoSuchMethodException
- InstantiationException
- IllegalAccessException
- ParseException
13. Give the list of some Runtime exceptions.
List of some common runtime exceptions in Java
- NullPointerException
- ArrayIndexOutOfBoundsException
- ArithmeticException
- IllegalArgumentException
- NumberFormatException
- UnsupportedOperationException
- ConcurrentModificationException
14. What is Error in Java?
In Java, an Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Errors are typically abnormalities that happen beyond the control of the user or programmer and are usually associated with the environment in which the application is running. Unlike exceptions, errors are often fatal and indicate a severe problem that a program cannot handle, such as system crash or a low-level system malfunction.
Common Types of Errors:
Here are some of the typical Error types you might encounter in Java:
- OutOfMemoryError
- Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
- StackOverflowError
- Thrown when a stack overflow occurs because an application recurses too deeply.
- NoClassDefFoundError
- Thrown if the Java Virtual Machine or a
ClassLoaderinstance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using thenewexpression) and no definition of the class could be found.
- Thrown if the Java Virtual Machine or a
- UnsatisfiedLinkError
- Thrown if the Java Virtual Machine cannot find an appropriate native-language definition of a method declared
native.
- Thrown if the Java Virtual Machine cannot find an appropriate native-language definition of a method declared
- InternalError
- Signals that an internal error occurred in the Java Virtual Machine but does not specify what the problem is.
- AssertionError
- Thrown to indicate that an assertion has failed. These are raised typically to catch attention during development and testing phases, after enabling assertions at runtime.
Example:
public class ExampleError {
public static void generateError() {
// Intentionally causing stack overflow
generateError();
}
public static void main(String[] args) {
try {
generateError();
} catch (StackOverflowError e) {
System.out.println("Caught StackOverflowError. Error message: " + e.getMessage());
}
}
}Explanation: In this example, the method generateError() recursively calls itself indefinitely, leading to a StackOverflowError. The error is caught in the main method, though typically, it’s not advisable to catch such serious problems as errors usually indicate something seriously wrong that should be corrected rather than handled at runtime.
Conclusion:
While you can catch errors just like exceptions, handling them usually involves corrective action related to configuration, or fixing underlying environmental issues or bugs. Generally, in production environments, the focus should be on identifying the cause of such serious issues and correcting them rather than attempting to recover from them programmatically.
15. What is Throwable?
In Java, Throwable is the superclass of all errors and exceptions in the Java language. It is the top class in the hierarchy of Java’s exception handling classes. All the classes that are used for exception handling are derived from the Throwable class, meaning that anything that can be thrown or caught as an exception or error in Java must be an instance of Throwable or one of its subclasses.
Main Subclasses of Throwable:
Throwable has two major direct subclasses:
- Exception: Intended for conditions that a user’s program should catch. This class is the superclass of all exceptions that are meant to be recoverable.
- Error: Intended for serious errors from which recovery is not usually possible, typically reflecting some abnormal condition in the JVM or underlying hardware.
Interview Questions on Abstract Class and Interface
This part of our website is covering selected questions that dive into the use of abstract classes and interfaces in Java. This section is designed to help both beginners and experienced developers to prepare the Abstract Class and Interface topic for their next java developer interview role. Whether you’re revising existing knowledge or taking new challenges, our “Abstract Class and Interface” section ensures you’re well-prepared to discuss these critical topics confidently in your interviews.
1. What is an abstract class in Java?
In Java, Abstract class is a class that is started with abstract keyword. It is a class that cannot be instantiated. It can have abstract and concrete methods both. It works as a blueprint for other classes.
Key characteristics of abstract classes
- Cannot be Instantiated: Abstract class cannot be instantiated using the
newkeyword. Attempting to do so will result in a compilation error. - May Contain Abstract Methods and Concrete Methods : Abstract class contains both abstract method (without body) and concrete method (with body).
- All fields are automatically public, static, and final in abstract class.
Example of Abstract Class
abstract class Shape {
// Fields
protected String color;
// Constructor
public Shape(String color) {
this.color = color;
}
// Abstract method to calculate area (to be implemented by subclasses)
public abstract double calculateArea();
// Concrete method
public void displayColor() {
System.out.println("Color: " + color);
}
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(String color, double length, double width) {
super(color);
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
public class AbstractClassExample {
public static void main(String[] args) {
Circle circle = new Circle("Yellow", 15.0);
Rectangle rectangle = new Rectangle("Green", 3.0, 5.0);
circle.displayColor();
System.out.println("Area of Circle: " + circle.calculateArea());
rectangle.displayColor();
System.out.println("Area of Rectangle: " + rectangle.calculateArea());
}
}
Output
Color: Yellow Area of Circle: 706.8583470577034 Color: Green Area of Rectangle: 15.0
2. Can you create an instance of an abstract class?
No we can’t. Attempting to create an object (instance) of an abstract class using the new keyword will result in a compilation error.
abstract class MyAbstractClass {
abstract void abstractMethod();
}
public class Main {
public static void main(String[] args) {
MyAbstractClass instance = new MyAbstractClass();
}
}Output
Main.java:7: error: MyAbstractClass is abstract; cannot be instantiated
MyAbstractClass instance = new MyAbstractClass();
3. How do you define constructors within an abstract class?
In an abstract class in Java, you can define constructors just like you would in a regular class.
Few Points to keep in mind
- Constructors in an abstract class can have access modifiers like
public,protected, orprivatejust like constructors in regular classes. - Constructors in an abstract class can participate in constructor chaining. This means that you can call one constructor from another using
this()orsuper()just as you would in a regular class.
Example:
abstract class Shape {
// Fields
protected String color;
// Constructor
public Shape(String color) {
this.color = color;
}
// Abstract method to calculate area (to be implemented by subclasses)
public abstract double calculateArea();
// Concrete method
public void displayColor() {
System.out.println("Color: " + color);
}
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(String color, double length, double width) {
super(color);
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
public class AbstractClassExample {
public static void main(String[] args) {
Circle circle = new Circle("Yellow", 15.0);
Rectangle rectangle = new Rectangle("Green", 3.0, 5.0);
circle.displayColor();
System.out.println("Area of Circle: " + circle.calculateArea());
rectangle.displayColor();
System.out.println("Area of Rectangle: " + rectangle.calculateArea());
}
}
Output
Color: Yellow Area of Circle: 706.8583470577034 Color: Green Area of Rectangle: 15.0
4. Can an abstract class extend another abstract class?
Yes
5. Explain scenarios where using an abstract class is appropriate.
Using an abstract class is appropriate in various scenarios where you want to provide a common base for a group of related classes while ensuring that certain methods are implemented by all subclasses.
Example
abstract class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// Abstract method for calculating shipping cost
public abstract double calculateShippingCost();
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
class ElectronicsProduct extends Product {
private double weight;
public ElectronicsProduct(String name, double price, double weight) {
super(name, price);
this.weight = weight;
}
@Override
public double calculateShippingCost() {
// Calculate shipping cost based on weight and price
return 0.05 * weight + 0.02 * getPrice();
}
}
class ClothingProduct extends Product {
private String size;
public ClothingProduct(String name, double price, String size) {
super(name, price);
this.size = size;
}
@Override
public double calculateShippingCost() {
// Calculate shipping cost based on clothing size and price
if (size.equals("Small")) {
return 0.03 * getPrice();
} else {
return 0.04 * getPrice();
}
}
}
class BookProduct extends Product {
private int pageCount;
public BookProduct(String name, double price, int pageCount) {
super(name, price);
this.pageCount = pageCount;
}
@Override
public double calculateShippingCost() {
// Calculate shipping cost based on page count
if (pageCount <= 100) {
return 2.0;
} else {
return 3.0;
}
}
}
public class ECommerceExample {
public static void main(String[] args) {
// Create instances of different product types
ElectronicsProduct laptop = new ElectronicsProduct("Laptop", 799.99, 5.0);
ClothingProduct shirt = new ClothingProduct("Shirt", 29.99, "Medium");
BookProduct novel = new BookProduct("Novel", 12.99, 250);
// Calculate and display shipping costs
System.out.println("Shipping cost for " + laptop.getName() + ": $" + laptop.calculateShippingCost());
System.out.println("Shipping cost for " + shirt.getName() + ": $" + shirt.calculateShippingCost());
System.out.println("Shipping cost for " + novel.getName() + ": $" + novel.calculateShippingCost());
}
}
Output
Shipping cost for Laptop: $16.2498 Shipping cost for Shirt: $1.1996 Shipping cost for Novel: $3.0
6. Can a concrete class (non-abstract) have abstract methods?
No, a concrete class (a class without the abstract keyword) cannot have abstract methods. Abstract methods are intended to be declared in abstract classes or interfaces, and they are meant to be implemented (i.e., given a body) by subclasses. Concrete classes are expected to provide implementations for all methods declared in their superclass or interface, including abstract methods.
7. What is an interface in Java?
In Java, an interface is a blueprint for a class. An interface defines a contract of methods that should must be implement by the class that is implementing that interface.
interface MyInterface {
void abstractMethod();
}class MyClass implements Interface1, Interface2 {
// Implement methods from Interface1 and Interface2
}8. Can an interface contain instance variables?
In Java, interfaces cannot contain instance variables (also known as fields or member variables) like classes can. Interfaces are primarily meant to declare method contracts and constants (static final variables), but they do not support instance variables.
Interfaces allow you to declare constants (static final variables) that are implicitly public, static, and final. These constants can be used to define shared values that implementing classes can access.
interface Constants {
int MAX_VALUE = 100; // Constant declaration
}
class MyClass implements Constants {
void printMaxValue() {
System.out.println(MAX_VALUE); // Accessing the constant from the interface
}
}
9. Can you provide default implementations for interface methods?
Yes, After java 8 we can define the default implementation for interface methods.
Example:
interface MyInterface {
void regularMethod();
default void defaultMethod() {
System.out.println("This is a default method.");
}
}
class MyClass implements MyInterface {
@Override
public void regularMethod() {
System.out.println("Implementation of regularMethod in MyClass.");
}
@Override
public void defaultMethod() {
System.out.println("Custom implementation of defaultMethod in MyClass.");
}
}
public class Main {
public static void main(String[] args) {
MyClass myObj = new MyClass();
myObj.regularMethod();
myObj.defaultMethod();
}
}10. Explain the significance of “default” and “static” methods in interfaces.
In Java 8, “default” and “static” methods in interfaces were introduced to enhance the flexibility and functionality of interfaces.
Default Methods:
- Default methods allow you to add new methods to existing interfaces without breaking backward compatibility with classes that already implement those interfaces. This is especially useful when evolving libraries and APIs.
- Default methods provide a default implementation for a method in an interface.
Example
interface Calculator {
int add(int a, int b);
default int subtract(int a, int b) {
return a - b;
}
}
class BasicCalculator implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new BasicCalculator();
int sum = calculator.add(25, 18);
System.out.println("Sum: " + sum);
int difference = calculator.subtract(25, 18);
System.out.println("Difference: " + difference);
}
}
Static Methods:
- Static methods in interfaces allow you to define utility methods that are related to the interface but do not depend on instance-specific data. These methods are invoked on the interface itself, not on instances.
- Java’s collection framework uses static methods in interfaces to create instances of collections with initial values (e.g.,
List.of(...),Set.of(...),Map.of(...)).
Example
interface MathOperations {
static double calculateRectangleArea(double length, double width) {
return length * width;
}
static double calculateCircleArea(double radius) {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
double rectangleArea = MathOperations.calculateRectangleArea(7.0, 4.0);
System.out.println("Rectangle Area: " + rectangleArea);
double circleArea = MathOperations.calculateCircleArea(2.5);
System.out.println("Circle Area: " + circleArea);
}
}
11. What is a functional interface in Java?
In Java, a functional interface is an interface that contains only one abstract method. Functional interfaces are also known as Single Abstract Method (SAM) interfaces. The concept of functional interfaces is closely associated with the introduction of lambda expressions in Java 8, which allows you to represent functional interfaces more concisely.
But functional interface can have multiple default methods or static methods without violating the functional interface contract.
Example
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething();
}12. Different ways to achieve Abstraction.
Abstraction is one of the fundamental principles of object-oriented programming (OOP). It allows us to model complex systems by simplifying and focusing on essential features by hiding the complex unnecessary details.
Several ways to achieve abstraction in OOP:
1). Abstract Classes: An abstract class is a class that cannot be instantiated and is typically used as a base class for other classes. It may contain abstract methods (methods without a body) that must be implemented by subclasses. Abstract classes can also have concrete methods with implementations.
abstract class Shape {
abstract void draw(); // Abstract method
abstract void display(){
System.out.println("display");
}
}
class Circle extends Shape {
void draw() {
// Implementation for drawing a circle
}
}
2). Interfaces: An interface contains only abstract methods without its implementation. It is also know as contract of methods. All the methods must be implemented by any class that implements the interface. Unlike abstract classes, interfaces can be implemented by multiple classes, allowing for multiple inheritances of behavior.
interface Drawable {
void draw();
void area();
}
class Circle implements Drawable {
void draw() {
// Implementation for drawing a circle
}
void area() {
// Implementation for calculating area of circle
}
}
There are many more ways like Encapsulation, Polymorphism, Enums etc.
13. Use of Abstract class
Abstract classes in Java are used to provide a common base or blueprint for other classes.
It is useful in creating Base Class, Defining Abstract Methods, Forcing Common Behavior, Enforcing a Design Contract, API Design, Restricting Instantiation etc.
14. How to call constructor of abstract class?
In Java, you can call the constructor of an abstract class using the super() keyword from the constructor of a concrete subclass that extends the abstract class. This allows you to initialize fields and perform common setup tasks defined in the abstract class’s constructor.
Example:
abstract class AbstractClass {
private int someField;
public AbstractClass(int initialValue) {
this.someField = initialValue;
// Other common initialization tasks, if any
}
// Other methods and fields, if any
}class ConcreteClass extends AbstractClass {
private String someOtherField;
public ConcreteClass(int initialValue, String value) {
super(initialValue); // Call the constructor of the abstract class
this.someOtherField = value;
// Additional initialization for the subclass
}
// Other methods and fields specific to the subclass
}15. Can we have constructor in interface?
In Java, interfaces cannot have constructors. Interface members are limited to abstract methods, default methods (introduced in Java 8), static methods (introduced in Java 8), and constant fields (static final variables).
Interview Questions on Multi Threading
Check out our “Multi-Threading” section, perfect for anyone getting ready for Java developer interviews. This part of our website has some most commonly asked java interview questions on multi-threading topic. Our questions help you learn everything from the basics, like threads and tasks, to more complex stuff like synchronization and dealing with shared data. This section is great for both new learners and people who already know some Java but want to get better of their java developer role. It helps you understand and practice these ideas, so you feel ready and confident to answer multi-threading questions in your interviews.
1. What is multithreading in Java?
Multithreading in Java refers to the concurrent execution of multiple threads within a Java program. A thread is a lightweight, independent unit of execution. Multiple threads can run concurrently in the same process. Multithreading allows a Java application to perform multiple tasks or processes in parallel, which can lead to improved performance and responsiveness.
2. How does multithreading differ from multitasking?
Multithreading and multitasking are related concepts. But both works at different levels.
Multithreading:
Multithreading means multiple threads are executing parallelly within a single process. Threads within the same process share the same memory space, allowing them to access shared data and communicate easily.
For Example: Multiple threads can be used to perform different tasks concurrently within the same process. Suppose one thread might handle user interface updates, while another performs background data processing.
Multitasking:
Multitasking means various process are running concurrently on a computer. Each process has its own isolated memory space, file handles, and system resources. Processes are isolated from each other, meaning they cannot directly access each other’s memory.
For Example: In a computer operating system, multitasking allows multiple independent applications or programs to run simultaneously. Suppose a word processor and a web browser can run as separate processes, each with its own memory space and resources.
3. How thread can created in Java?
In Java, threads can be created using either the Thread class or by implementing the Runnable interface.
1). Extending the Thread Class:
Thread can be created by extending the Thread class and overriding its run() method. After that you can create an instance of your custom thread class and call the start() method to initiate the execution of the thread.
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}Output
Thread: 0 Thread: 1 Thread: 2 Thread: 3 Thread: 4 Thread: 5 Thread: 6 Thread: 7 Thread: 8 Thread: 9
2). Implementing the Runnable Interface:
Thread is also created by implementing the Runnable interface. This approach allows better flexibility because it separates the thread’s behavior from the thread itself. You create an instance of your custom class that implements Runnable and pass it to a Thread object. Then, you call the start() method on the Thread object.
class MyRunnable implements Runnable {
public void run() {
// This code will be executed by the thread
for (int i = 0; i < 10; i++) {
System.out.println("Thread: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // Start the thread
}
}
Output
Thread: 0 Thread: 1 Thread: 2 Thread: 3 Thread: 4 Thread: 5 Thread: 6 Thread: 7 Thread: 8 Thread: 9
4. Tell Me the various states in the lifecycle of a thread?
In Java, a thread goes through several states during its lifecycle. These states represent different stages in the execution of a thread.
Various State of thread lifecycle
New: When a thread is created but has not yet started executing. In this state, the Thread object has been instantiated, but the start() method has not been called to begin execution.
Runnable/Ready: After a thread is created and is ready to run, it enters the “Runnable” or “Ready” state. This means the thread is waiting to be scheduled by the operating system’s thread scheduler to run on a CPU core. It is not guaranteed to be running yet, as it depends on the scheduling algorithm.
Running: When a thread is selected by the thread scheduler and is executing on a CPU core, it is said to be in the “Running” state. At any given time, only one thread from a multi-threaded process can be in the running state on a single CPU core.
Blocked/Waiting: Threads can enter the “Blocked” or “Waiting” state for various reasons, such as when they are waiting for a resource, waiting for user input, or sleeping for a certain amount of time. In this state, the thread is not executing and is waiting for a specific condition to be met before it can become runnable again.
5. What is wait(), notify() and sleep() methods in thread synchronization?
In Java, wait(), notify(), and sleep() are methods commonly used in thread synchronization and management.
1). wait():
wait()is a method available in theObjectclass in Java.- It is used to make a thread to give up its lock on an object and enter a waiting state.
- When a thread calls
wait(), it releases the lock on the object so that other threads can acquire the lock and execute the synchronized methods or blocks on the same object. - The calling thread remains in a waiting state until another thread notifies it using the
notify()ornotifyAll()method on the same object or until it is interrupted. wait()is often used for thread synchronization and communication between threads.
Example
synchronized (sharedObject) {
while (!condition) {
sharedObject.wait(); // Release the lock and wait
}
// Perform some action when the condition is met
}2. notify() and notifyAll():
notify()andnotifyAll()are methods available in theObjectclass.- They are used to wake up one or all waiting threads that are in blocked state.
notify()wakes up a single waiting thread that is blocked.notifyAll()wakes up all waiting threads that is blocked.- These methods are used with
wait()for implementing more complex thread synchronization patterns.
synchronized (sharedObject) {
sharedObject.notifyAll(); // Wake up all waiting threads
}3. sleep():
sleep()is a method provided by theThreadclass in Java.- It is used to pause the execution of the current thread for a specified amount of time (in milliseconds) that is given in sleep method. If specified time passed execution start again.
- Unlike
wait(),sleep()does not release the lock on any object. The thread holding the lock will continue to hold it while sleeping. sleep()is often used for introducing time delays in thread execution or for scheduling periodic tasks.
try {
Thread.sleep(1000); // Sleep for 1 second
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}In summary, wait(), notify(), and sleep() are essential tools for managing and synchronizing threads in Java. wait() and notify() are used for communication and synchronization between threads, while sleep() is used for introducing time delays in thread execution.
6. What are thread priorities in Java?
In Java, thread priorities are a way to influence the scheduling of threads by indicating their relative importance or urgency to the Java Virtual Machine (JVM). Thread priorities help the JVM’s thread scheduler decide which thread should be executed next when multiple threads are ready to run. Thread priorities are represented as integers and defined as constants in the Thread class. The available thread priority levels in Java are:
- MIN_PRIORITY (1): This is the lowest thread priority level. Threads with this priority are considered to be of minimal importance, and the JVM scheduler will give them less preference when deciding which thread to execute.
- NORM_PRIORITY (5): This is the default thread priority level. Threads that are created without explicitly setting a priority will be assigned this priority. It represents a moderate level of importance.
- MAX_PRIORITY (10): This is the highest thread priority level. Threads with this priority are considered to be of utmost importance, and the JVM scheduler will give them higher preference when selecting the next thread to run.
You can set a thread’s priority using the setPriority(int priority) method of the Thread class, where priority is an integer value representing the desired priority level.
For example:
Thread thread = new Thread(() -> {
// Thread logic here
});
thread.setPriority(Thread.MAX_PRIORITY); // Set the thread's priority to the maximumHowever, there are some important points to keep in mind regarding thread priorities in Java:
- The exact behavior of thread priorities can vary between different Java Virtual Machine implementations and operating systems. While priorities provide a hint to the JVM scheduler, they do not guarantee a strict execution order.
- Relying solely on thread priorities for critical synchronization or coordination between threads is not recommended. Proper synchronization mechanisms such as
wait(),notify(), andsynchronizedblocks should be used to ensure thread safety and correct coordination. - Setting thread priorities to extreme values (MIN_PRIORITY or MAX_PRIORITY) should be done with caution, as it may disrupt the overall system’s fairness and performance.
- In modern multi-core processors, thread priorities might have limited impact on performance, as other factors like thread affinity and context switching play a role in determining the execution order.
7. What is a deadlock and how does it occur in multi threading?
A deadlock is a common problem in multi-threading and concurrent programming. This is a situation where two or more threads are unable to proceed with their execution because they each are waiting for a resource that the other thread hold or control. In a deadlock situation, the threads effectively become stuck and the program cannot make any progress.
Example:
Thread A and Thread B are both running concurrently and need two resources: Resource X and Resource Y.
- Thread A acquires Resource X.
- Thread B acquires Resource Y.
Now, both threads are in a “Hold and Wait” state:
- Thread A is holding Resource X and waiting for Resource Y.
- Thread B is holding Resource Y and waiting for Resource X.
Neither thread can proceed because they are each waiting for a resource held by the other, creating a circular dependency. This is a deadlock.
8. What is the Java Concurrency API?
The Java Concurrency API is a set of high-level, thread-safe utility classes and interfaces introduced in Java to simplify and enhance concurrent and parallel programming. It provides tools for managing threads, synchronizing access to shared resources, and coordinating the execution of concurrent tasks. The Java Concurrency API was introduced in Java 5 (Java 1.5) as part of the Java platform’s ongoing efforts to support multi-threading and concurrent programming.
9. Tell me about a thread pool and why is it used?
A thread pool in computing is a collection of pre-initialized threads that stand ready to execute tasks. This pool manages a queue of tasks to be executed alongside a set of worker threads, which handle the tasks in the queue. When a new task is introduced, it’s enqueued, and a thread from the pool is assigned to execute it. Once the thread completes the task, it reports back to the pool and becomes available for the next task. This approach to thread management and task execution offers several advantages over spawning a new thread for each task in terms of resource management, performance, and system stability.
Implementing Thread Pools in Java:
Java provides built-in support for thread pools through the java.util.concurrent package, which includes several classes and interfaces for managing a set of threads, such as Executor, ExecutorService, and ThreadPoolExecutor. Developers can utilize these to easily implement efficient, scalable thread management in their applications without having to manually manage thread life cycles.
10. What is volatile keyword in Java?
In Java, the volatile keyword is used as a modifier for instance variables to indicate that their values may be changed by multiple threads concurrently. It ensures that the variable is always read from and written to the main memory, rather than from thread-specific caches, ensuring visibility across threads.
Example:
class SharedResource {
private volatile boolean flag = false;
public void setFlagTrue() {
flag = true;
}
public boolean isFlagTrue() {
return flag;
}
}In the above example, the flag variable is declared as volatile to ensure that changes made to it by one thread are immediately visible to other threads. The setFlagTrue() method sets the flag to true, and the isFlagTrue() method checks its value.
Explanation with example
Imagine you have a flag (like a red flag) in a park, and many kids (threads) are playing in the park. When one kid puts up the red flag, it’s a signal for everyone else to stop playing and pay attention.
In Java, when you declare a variable as volatile, it’s like putting up a red flag. It means that if one part of your program changes the value of that variable, all the other parts of your program will see that change right away, even if they are running at the same time. So, it helps make sure that everyone is on the same page and knows the latest value of that variable.
11. Difference in Synchronization and Lock.
In the context of concurrent programming, synchronization and locks are both mechanisms used to control access to shared resources by multiple threads, ensuring data consistency and preventing race conditions. While they aim to solve similar problems, they do so in different ways and offer different levels of control and flexibility.
Synchronization
Synchronization in Java is a built-in mechanism that restricts access to resources by ensuring that only one thread can access a synchronized block of code at a time. It can be applied to methods or blocks of code within methods.
Locks
Locks provide a more flexible way to achieve synchronization, allowing more detailed control over lock acquisition, release, and the conditions under which locks are held. Java’s java.util.concurrent.locks package provides several lock implementations, including ReentrantLock, which offers advanced capabilities for thread synchronization.
Synchronization vs Lock
| Feature | Synchronization | Locks |
|---|---|---|
| Mechanism | Built-in language feature | Part of the java.util.concurrent.locks package |
| Granularity | Method-level or block-level within a method | Fine-grained control with explicit lock and unlock operations |
| Control | Automatic lock management by the JVM | Manual lock management; developers explicitly lock and unlock |
| Flexibility | Less flexible; synchronized blocks/methods only | More flexible; offers try, timed, and interruptible lock operations |
| Features | Basic locking mechanism | Advanced features like conditions, tryLock, lockInterruptibly, fairness |
| Usage | Simpler cases where a block of code or method needs protection | Complex scenarios requiring detailed control, conditions, or fairness |
| Ease of Use | Easier to use; less boilerplate | Requires careful management to avoid deadlocks and ensure unlocking |
| Scope of Lock | On the object or class (for static methods) | Can lock on any scope as defined by the lock object |
| Condition Variables | Not directly supported | Supported through Condition objects associated with the Lock |
| Best Used For | Quick synchronization of methods or blocks with minimal logic | Complex synchronization scenarios with high concurrency requirements |
12. What is volatile, synchronized and transient?
volatile, synchronized, and transient are three different keywords in Java used to modify the behavior of variables and methods in the context of multi-threading, data storage, and object serialization.
1). volatile
volatilekeyword is used with a variable to declare it as “volatile,”. It means its value may be changed by multiple threads concurrently.- When a variable is declared as
volatile, it ensures that any read or write operation on that variable is guaranteed to be visible to all threads. Means read value from the main memory. volatiledoes not provide atomicity for compound operations or mutual exclusion, so it is typically used for simple variables or flags that are accessed by multiple threads without complex operations.
Example
private volatile int counter = 0;2). synchronized
synchronizedis a keyword used to create synchronized blocks and methods. It helps to control access to critical sections of code by multiple threads that is written within synchronized blocks and methods.- When a block of code or method is marked as
synchronized, only one thread can execute that block or method at a time, ensuring mutual exclusion. - It is used for protecting shared resources, achieving thread safety, and preventing race conditions.
Example
//synchronized method
public synchronized void synchronizedMethod() {
// Thread-safe code
}
//synchronized Block example
public void increment() {
synchronized (this) { // Start of synchronized block
count++;
} // End of synchronized block
}3). transient
transientis used to indicate that a variable should not be included when an object is serialized.- When an object is serialized (converted to a byte stream), the values of its non-transient fields are saved. Transient fields are not included in the serialized form and are set to their default values upon deserialization.
- It is often used for variables that should not be serialized, such as temporary or derived values.
Example
private transient int tempValue;In summary:
volatileis used for variables that can be changed by multiple threads and ensures visibility but not atomicity or mutual exclusion.synchronizedis used to create synchronized blocks or methods that provide mutual exclusion for thread-safe access to shared resources.transientis used to mark variables that should not be serialized when an object is converted to a byte stream.
13. Runnable vs Callable.
In Java, both Runnable and Callable are interfaces that allow you to define tasks that can be executed by threads.
Runnable
Runnableis a functional interface introduced in Java. It defines a single method:void run(), which represents the task to be executed.- Runnable tasks do not return a result. The
run()method has avoidreturn type, which means it cannot return a value or throw checked exceptions. - Runnable tasks are often used for simple, fire-and-forget tasks that do not need to return a result, such as performing background calculations, logging, or updating a shared data structure.
Callable
Callableis also a functional interface introduced in Java. It defines a single method:V call() throws Exception, whereVrepresents the type of the result to be returned, and it can throw checked exceptions.- Callable tasks return a result, and you can specify the type of the result by parameterizing the
Callableinterface with a type (e.g.,Callable<Integer>). - Callable tasks are used when you need to perform a task that produces a result and may throw exceptions. Examples include performing computations, making network requests, or querying databases.
- Callable tasks are typically executed using an
ExecutorService(e.g.,ThreadPoolExecutor). They are also executed asynchronously, and you can obtain the result of aCallabletask using aFutureobject, which allows you to retrieve the result once the task is complete.
Example Of Runnable and Callable
import java.util.concurrent.*;
public class RunnableVsCallableExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Runnable example
Runnable runnableTask = () -> {
System.out.println("Runnable task is running.");
};
Thread runnableThread = new Thread(runnableTask);
runnableThread.start();
// Callable example
Callable<Integer> callableTask = () -> {
System.out.println("Callable task is running.");
return 42;
};
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(callableTask);
int result = future.get();
System.out.println("Callable task result: " + result);
executor.shutdown();
}
}
14. Difference between synchronization and volatile keyword.
Both the synchronization and volatile keywords in Java are used in multi-threaded programming to address concurrency-related issues.
| Aspect | Synchronization | Volatile |
|---|---|---|
| Purpose | Provides mutual exclusion, controls access to shared resources, enforces thread safety. | Ensures visibility of variable changes across threads. |
| Use Cases | Protecting shared resources, enforcing mutual exclusion, coordinating thread execution. | Flags, status variables, simple counters, non-atomic read/write operations. |
| Atomicity | Provides atomicity for synchronized blocks or methods. | Provides atomic read/write operations for the declared variable. Does not ensure atomicity for complex operations. |
| Controlling Access | Controls access to code blocks or methods, allowing one thread at a time to execute them. | Controls visibility of variable changes; does not control access to code blocks or methods. |
| Complexity | Involves acquiring and releasing locks, suitable for fine-grained control over thread access. | Simpler to use, primarily for ensuring visibility without complex synchronization. |
| Multiple Variables | Can be used to synchronize multiple variables together within a synchronized block or method. | Applies to individual variables, and multiple volatile variables are not synchronized together. |
| Code Structure | Requires defining synchronized blocks or methods explicitly. | Involves declaring individual variables as volatile. |
| Use with Methods | Can be used to synchronize entire methods. | Cannot be used with methods; applies only to variables. |
| Concurrent Access to Code | Enforces mutual exclusion and sequential execution of synchronized sections. | Does not control access to code sections; focuses on variable visibility. |
15. Thread communication (wait notify notifyall).
wait(): When a thread calls wait() on an object, it releases the lock associated with that object, allowing other threads to acquire the lock and execute synchronized methods or blocks on the same object.
Example
synchronized (sharedObject) {
while (!condition) {
sharedObject.wait(); // Release the lock and wait
}
// Perform some action when the condition is met
}notify(): notify() method is used to wake up one waiting thread that is blocked by calling wait() method.
synchronized (sharedObject) {
// Modify some shared state
sharedObject.notify(); // Wake up one waiting thread
}notifyAll(): notifyAll() method is used to wake up all waiting threads that are blocked.
synchronized (sharedObject) {
// Modify some shared state
sharedObject.notifyAll(); // Wake up all waiting threads
}Interview Questions on Garbage Collection
Visit our “Garbage Collection” section to prepare for your next Java developer interviews that ask about managing memory. This part of our website offers a bunch of questions about garbage collection in Java, which is all about how the program cleans up memory it no longer needs. You’ll find questions that explain the basics of how garbage collection works and more detailed topics like different types of collectors and how they help keep programs running smoothly. This section is great for everyone, from beginners, freshers experienced Java developer. Those who already know some Java and want to learn more this section will also help them. It helps you understand important concepts and get ready to handle garbage collection questions with confidence in your interviews.
1. What is garbage collection in Java?
Garbage collection in Java is the process by which the Java Virtual Machine (JVM) automatically manages the memory used by a Java application. It involves identifying and reclaiming memory occupied by objects that are no longer reachable.
And this non reachable objects are no longer needed to Java programs. Garbage collection helps prevent memory leaks and ensures efficient memory utilization in Java programs.
Example
public class GarbageCollectionExample{
public void finalize(){
System.out.println("Object is garbage collected");
}
public static void main(String args[]){
GarbageCollectionExample s1=new GarbageCollectionExample();
GarbageCollectionExample s2=new GarbageCollectionExample();
s1=null;
s2=null;
System.gc();
}
} Output
Object is garbage collected Object is garbage collected
2. Explain the purpose of garbage collection in terms of memory management.
Garbage collection (GC) is an automatic memory management feature present in various programming languages, including Java, C#, Python, and others. Its primary purpose is to reclaim memory occupied by objects (or data structures) that are no longer in use by a program, thus preventing memory leaks and ensuring the efficient utilization of available memory resources. Garbage collection plays a crucial role in managing the lifecycle of objects dynamically, freeing developers from the manual task of memory allocation and deallocation.
Key Purposes of Garbage Collection in Memory Management:
- Automatic Memory Reclamation: GC automates the process of identifying and freeing unused memory, reducing the risk of memory leaks that can occur in manual memory management and potentially lead to application crashes or performance degradation over time.
- Optimization of Available Memory: By reclaiming memory from objects that are no longer needed, GC optimizes the use of available memory, ensuring that applications can continue to allocate memory for new objects and operate efficiently.
- Enhancing Program Stability and Safety: Automatic garbage collection minimizes errors related to memory management, such as double frees, dangling pointers, and incorrect deallocation, which can lead to undefined behavior, security vulnerabilities, and program instability.
- Simplification of Programming: GC abstracts away the complexity of manual memory management, allowing developers to focus more on the business logic of their applications rather than on the intricacies of allocating and freeing memory. This simplification can lead to more readable and maintainable code.
- Enabling Complex Data Structures: With automatic memory management, programmers can create and use complex, dynamic data structures without worrying about the lifecycles of the memory that those structures occupy. This freedom can encourage the development of more sophisticated algorithms and applications.
3. How does the Java Virtual Machine (JVM) determine when an object is eligible for garbage collection?
The Java Virtual Machine (JVM) determines that an object is eligible for garbage collection primarily based on its reachability — specifically, whether there are any live references to that object from the application’s roots. The roots include static variables, local variables, active threads, and other special references within the JVM. An object becomes eligible for garbage collection when it cannot be reached through any reference chain starting from these roots.
In addition to reachability, the JVM also considers other factors for garbage collection eligibility, like whether an object’s finalize() method has been invoked (if it overrides Object.finalize()). After finalization, if no references to the object are created, it becomes eligible for reclamation.
4. Major, Minor and Full Garbage collection
Garbage collection in Java can be categorized into three main types based on the scope and purpose of the collection:
Minor Garbage Collection (Young Generation):
- Scope: Focuses on the youngest generation (also known as the “young” or “eden” generation) of objects in the Java heap.
- Purpose: Identifies and collects short-lived objects that are likely to become garbage quickly, leaving behind longer-lived objects in the older generation.
- Algorithm: Typically uses fast, low-pause-time algorithms like the “copying” or “semi-space” collector.
- Frequency: Occurs more frequently because young objects tend to have shorter lifespans.
Major Garbage Collection (Old Generation):
- Scope: Targets the older generation of objects in the heap, which includes objects that have survived one or more minor garbage collections.
- Purpose: Reclaims memory occupied by objects that have been in memory for a longer time and are less likely to become garbage.
- Algorithm: Employs more comprehensive and potentially slower algorithms like the “mark-sweep-compact” collector.
- Frequency: Occurs less frequently compared to minor garbage collection.
Full Garbage Collection:
- Scope: Involves the entire heap, including both young and old generations.
- Purpose: Reclaims memory across the entire heap, including the young and old generations, by identifying and collecting all unreachable objects.
- Algorithm: Utilizes comprehensive garbage collection algorithms to reclaim memory globally.
- Frequency: Occurs infrequently and usually when the JVM determines that there is a shortage of memory or after multiple attempts at minor and major garbage collection have not freed enough memory.
5. Types of Garbage collection
The Java Virtual Machine (JVM) offers several garbage collectors (GCs) designed to cater to different types of applications, from small applications running on single-threaded environments to large, server-side applications requiring high throughput and low latency. Each garbage collector has its own approach to managing memory and reclaiming unused space.
Types of Garbage collection
1. Serial Garbage Collector
- Type: It’s a single-threaded garbage collector, using just one CPU or thread for GC activities.
- Operation Mode: It performs the garbage collection for the Young Generation and the Old Generation (Tenured Generation) serially.
- Use Case: It’s best suited for single-threaded environments and applications with small data sets where pause time is not a critical concern, such as standalone desktop applications or simple tools.
- Pros: Simplicity and minimal CPU overhead, making it efficient for small applications.
- Cons: Not suitable for multi-threaded applications or servers as it can cause noticeable pauses during garbage collection.
2. Parallel Garbage Collector (Throughput Collector)
- Type: A multi-threaded garbage collector that uses multiple threads for Young Generation garbage collection.
- Operation Mode: Garbage collection is performed in parallel to improve throughput. However, it still stops all application threads during garbage collection phases (Stop-The-World events).
- Use Case: Optimized for throughput and performance, making it suitable for multi-threaded applications, such as backend services, where short GC pause times are acceptable but overall throughput is critical.
- Pros: Higher throughput and better CPU utilization than the serial collector.
- Cons: Can still experience significant pause times for Old Generation garbage collection.
3. Concurrent Mark Sweep (CMS) Garbage Collector
- Type: Targets shorter garbage collection pauses by doing most of its work concurrently with the application threads.
- Operation Mode: It marks live objects and sweeps up the unreferenced objects without stopping the application threads (concurrently). However, it still requires occasional pauses, especially during the initial and final mark phases.
- Use Case: Designed for applications that require low pause times, such as interactive web applications.
- Pros: Reduces pause times compared to serial and parallel collectors.
- Cons: Can lead to more CPU overhead and might encounter fragmentation issues.
4. Garbage First (G1) Garbage Collector
- Type: A server-style garbage collector aimed at multi-processor machines with large memory space.
- Operation Mode: Divides the heap into regions and manages them based on set performance goals. It performs garbage collection in a way that prioritizes regions with the most garbage first, hence the name.
- Use Case: Suitable for applications running on multi-core machines with large heaps, where predictable pause times are necessary.
- Pros: Offers more predictable pause times than CMS and can efficiently manage larger heaps. It aims to provide a good balance between throughput and pause times.
- Cons: Might require tuning to achieve optimal performance. It’s more complex than the other collectors.
6. How does the Young Generation collection work during garbage collection?
The Young Generation (also known as the “nursery”) in the Java Virtual Machine (JVM) refers to the part of the heap where newly created objects are allocated. Garbage collection in the Young Generation is crucial because most objects are short-lived, and frequent collection helps reclaim memory quickly and efficiently. The process of garbage collection in the Young Generation is designed to take advantage of this “weak generational hypothesis,” which posits that most objects die young.
The Process of Young Generation Garbage Collection:
- Allocation: New objects are allocated in the Young Generation, which is typically divided into three parts: one Eden space and two Survivor spaces (S0 and S1).
- Minor GC (Garbage Collection): When the Eden space becomes full, a Minor GC is triggered. Objects that are still alive are moved to one of the Survivor spaces (S0 or S1), assuming they don’t already reside in a Survivor space. If an object is already in a Survivor space and survives garbage collection, it may be moved to the other Survivor space or, based on its age, promoted to the Old Generation.
- Age Threshold: Objects in the Young Generation have an age associated with them, which is incremented every time they survive a Minor GC. Once an object reaches a certain age threshold, it is promoted to the Old Generation to avoid the cost of being copied between Survivor spaces. The age threshold is adjustable and can be tuned based on the application’s behavior.
- Copying Algorithm: The garbage collection process in the Young Generation is typically implemented using a copying algorithm. Live objects are copied from Eden to a Survivor space, or from one Survivor space to the other. Because only live objects are copied and because most objects die young, the copying process is efficient as it doesn’t have to deal with the entire set of objects in the Young Generation.
- Efficiency: The efficiency of the Young Generation garbage collection comes from the fact that it deals with a relatively small portion of the heap and that it can quickly clear out space by moving the relatively few surviving objects. This process is much faster than scanning the entire heap, which includes the Old Generation.
7. How does the old Generation collection work during garbage collection?
The Old Generation (also known as the Tenured Generation) in the Java Virtual Machine (JVM) is the part of the heap that holds objects that have survived multiple garbage collection cycles in the Young Generation. Because objects in the Old Generation are expected to live longer, garbage collection in this area (also known as a Major GC or Full GC) is handled differently and less frequently than in the Young Generation. The goal is to efficiently reclaim space from objects that are no longer in use without excessively impacting application performance.
The Process of Old Generation Garbage Collection:
- Triggering Conditions: Major GC is typically triggered when the Old Generation becomes full or when certain thresholds are met. It can also be triggered by explicit calls to
System.gc(), though this is generally discouraged due to its unpredictable impact on performance. - Marking Phase: The garbage collector starts by identifying which objects are still reachable. It begins from a set of root references (like static fields, thread stacks, etc.) and marks all objects that can be transitively reached from these roots. Unreachable objects are considered eligible for garbage collection.
- Sweeping/Deleting Phase: After marking live objects, the collector sweeps through the Old Generation, deallocating memory occupied by unmarked (unreachable) objects. This phase effectively cleans up memory used by dead objects.
- Compaction Phase (Optional but common in many collectors): To combat memory fragmentation, some garbage collectors will compact the remaining objects, moving them so they occupy a contiguous block of memory. This makes future allocations more efficient but adds overhead to the garbage collection process.
8. What is memory leak?
A memory leak is a situation in computer programming where a program fails to release memory (RAM) that it has allocated but is no longer in use. As a result, the program consumes increasing amounts of memory over time, eventually leading to performance degradation or even a program crash.
Memory leaks can occur in languages that do not have automatic memory management (e.g., C and C++) and in languages with garbage collection (e.g., Java and C#) when references to objects are unintentionally retained, preventing the garbage collector from reclaiming the memory.
Some common scenarios
- Failure to Release Allocated Memory: If a program allocates memory using functions like
malloc()but forgets to callfree()to release that memory, a memory leak occurs. - Unclosed Resources: Memory leaks can occur when resources other than memory, such as file handles, database connections, or network sockets, are not properly closed or released after use.
Interview Questions on Java 8
Head over to our “Java 8” section to gear up for Java interviews that focus on the features introduced in Java 8. This part of our website is packed with questions about the new additions and improvements made in Java 8, like lambda expressions, stream API, and new date and time API. Our questions range from simple explanations to more complex uses of these features. This section is designed for freshers and experienced Java developers. Whether you’re just starting to learn Java or you’re refreshing your skills this section is also good to revise the concepts. It’s a great place to understand and practice these Java 8 concepts and prepare for interviews, so you can feel sure and ready to answer any Java 8 questions in your interviews.
1. What are lambda expressions in Java?
Lambda expression allow us to define and use anonymous functions in a more concise and expressive way. Lambda expressions make it easier to represent instances of single-method interfaces (functional interfaces) using a shorter and more readable syntax.
Lambda expressions have a compact syntax, consisting of parameter list, an arrow (->), and a body. The body can be an expression or a block of statements.
Syntax
(parameters) -> expression
(parameters) -> { statements }2. What is functional interfaces and their role in using lambda expressions.
Functional interfaces play a crucial role in Java when using lambda expressions. They are closely tied to lambda expressions and are a fundamental concept in the context of functional programming in Java.
The primary role of functional interfaces is to serve as a target type for lambda expressions. Lambda expressions provide a way to create instances of functional interfaces by implementing the single abstract method defined by the interface. This enables the use of lambda expressions to define concise and expressive code for specific behaviors.
Here’s how functional interfaces and lambda expressions work together:
Functional Interface Declaration:
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething(); // The single abstract method
}Using Lambda Expressions:
MyFunctionalInterface myLambda = () -> {
// Implementation of the doSomething method
System.out.println("Doing something...");
};Invoking Lambda Expressions:
myLambda.doSomething(); // Invoking the lambda expression3. Tell me about built-in functional interfaces like Consumer, Supplier, and Predicate.
Built-in functional interfaces like Consumer, Supplier, and Predicate are part of the java.util.function package in Java. These functional interfaces are widely used in Java for various functional programming tasks, especially when working with collections, streams, and lambdas.
Overview of these built-in functional interfaces:
Consumer
- Purpose: The
Consumerinterface represents an operation that accepts an input value, performs an action with that value, and returns no result. - Method: It has a single abstract method
void accept(T t)that takes an argument of typeTand performs some action on it. - Available in import java.util.function.*;
- Example: You can use
Consumerto iterate over a collection and apply an action to each element.
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
printUpperCase.accept("interview Expert");
}
}Output
INTERVIEW EXPERT
Supplier
- Purpose: The
Supplierinterface represents a supplier of results, or in other words, a factory for creating objects. It does not accept any arguments but produces a value. - Method: It has a single abstract method
T get()that returns a result of typeT. - Example: You can use
Supplierto generate values lazily.
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier<Integer> randomNumberSupplier = () -> (int) (Math.random()*100);
int randomValue = randomNumberSupplier.get();
System.out.println(randomValue);
}
}Output
84
Predicate
- Purpose: The
Predicateinterface represents a boolean-valued function that tests a condition on an input and returnstrueorfalse. - Method: It has a single abstract method
boolean test(T t)that takes an argument of typeTand returns a boolean result. - Example: You can use
Predicateto filter elements in a collection based on a condition.
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
boolean result = isEven.test(8);
System.out.println(result);
}
}Output
true
4. What is the Stream API in Java 8?
Streams enable you to perform various data processing operations on collections of data, such as lists, arrays, or even I/O channels, using functional-style programming.
Streams offer a wide range of operations, including filtering, mapping, reducing, sorting, and more. These operations simplify common data manipulation tasks.
Characteristics and benefits of the Stream API
- Functional Programming
- Declarative Style
- Lazy Evaluation
- Pipelining
- Parallelism
Example of How to use the Stream API
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
}
}Output
ALICE
5. Explain the use of streams to filter, map, and reduce data.
Streams in Java are designed to make data manipulation and processing more efficient and expressive. Three common operations you can perform on streams are filtering, mapping, and reducing data.
1). Filtering Data:
The filter operation allows you to select elements from a stream that match a given condition. It’s used to eliminate unwanted elements, leaving only those that satisfy the specified criteria.
Example
find the even number
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter the even numbers
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
evenNumbers.forEach(System.out::println);
}
}Output
2 4 6 8 10
2). Mapping Data:
The map operation transforms each element in a stream into another value. It’s useful when you want to apply a function to each element in a stream and create a new stream of transformed values.
Example
Find the length of each string in a list.
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
nameLengths.forEach(System.out::println);
}
}Output
5 3 7
3). Reducing Data:
The reduce operation is used to combine the elements of a stream into a single result. It allows you to perform aggregation operations like summing, finding the maximum, or concatenating strings.
Example
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("sum = "+ sum);
}
}Output
sum = 15
6. What is default methods in interfaces?
Default methods, also known as defender methods, are a feature introduced in Java 8 to allow interfaces to have method implementations.
Prior to Java 8, interfaces could only declare abstract methods, which meant that any class implementing an interface had to provide concrete implementations for all of its methods. Default methods provide a way to add new methods to existing interfaces without breaking backward compatibility with classes that implement those interfaces.
Example
import java.util.*;
interface DefaultImplementation{
int data = 5;
default int defaultData(){
return data;
}
}
public class Main implements DefaultImplementation{
public static void main(String[] args) {
Main main = new Main();
System.out.println("sum = "+ main.defaultData());
}
}Output
sum = 5
7. Explain Optional class in Java with examples.
The Optional class in Java is a container class introduced in Java 8 that represents an optional value, meaning it may or may not contain a non-null value.
It’s designed to help avoid NullPointerExceptions by providing a way to handle cases where a value may be absent.
1. Creating Optional Objects:
You can create an Optional object using the factory methods of, ofNullable, or by using empty.
Optional<String> emptyOptional = Optional.empty(); // Represents an empty optional.
Optional<String> nonEmptyOptional = Optional.of("Hello"); // Represents a non-null value.
Optional<String> nullableOptional = Optional.ofNullable(null); // Represents a potentially null value.
2. Accessing Values:
You can use get() method to retrieve the value if it exists, but be cautious because it can throw a NoSuchElementException if the value is absent.
Optional<String> optional = Optional.of("Hello");
String value = optional.get(); // Gets the value if present3. Checking for Value Presence:
You can use methods like isPresent() to check if a value is present in the Optional.
Optional<String> optional = Optional.of("Hello");
boolean isPresent = optional.isPresent(); // Checks if a value is present4. Handling Absent Values:
To handle cases where a value is absent, you can use methods like orElse or orElseGet to provide a default value or a value from a supplier.
Optional<String> optional = Optional.empty();
String result = optional.orElse("Default Value"); // Provides a default valueOptional<String> optional = Optional.empty();
String result = optional.orElseGet(() -> generateDefaultValue()); // Provides a default value using a supplier5. Conditional Execution:
You can use methods like ifPresent to execute a specific action when a value is present.
Optional<String> optional = Optional.of("Hello");
optional.ifPresent(value -> System.out.println("Value is present: " + value));6. Transforming Values:
You can apply transformations to the contained value using methods like map.
Optional<String> optional = Optional.of("Hello");
Optional<Integer> lengthOptional = optional.map(String::length); // Transforms the value7. Chaining Operations:
You can chain multiple Optional operations together to perform a series of actions.
Optional<String> optional = Optional.of("Hello");
int result = optional.map(String::length)
.filter(len -> len > 5)
.orElse(0); // Chained operations8. Avoiding NullPointerExceptions:
Using Optional can help avoid NullPointerExceptions when dealing with potentially null values.
String potentiallyNullValue = null;
Optional<String> optional = Optional.ofNullable(potentiallyNullValue);9. Using ifPresentOrElse (Java 9 and later):
In Java 9 and later, you can use ifPresentOrElse to provide different actions based on whether the value is present or not.
Optional<String> optional = Optional.empty();
optional.ifPresentOrElse(
value -> System.out.println("Value is present: " + value),
() -> System.out.println("Value is absent")
);8. Describe the new Date and Time API.
The new Date and Time API in Java, introduced in Java 8, is a comprehensive and more intuitive API for handling date and time-related operations.
It addresses the limitations of the old java.util.Date and java.util.Calendar classes, providing a more powerful and flexible solution for working with dates and times.
Key features and components of the new Date and Time API
- Immutable: The Date and Time API introduces immutable classes for representing date and time objects.
- Clear Separation: The API clearly separates the concepts of date (year, month, day) and time (hour, minute, second, nanosecond), allowing for more precise operations.
- Comprehensive Classes: Some classes by new date time api are LocalDate, LocalTime, LocalDateTime, ZonedDateTime, OffsetDateTime, Instant, Period and Duration etc.
Example
package datatimeapi;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeExample {
public static void main(String[] args) {
// Creating a LocalDate
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
System.out.println("Cureent Date: " + date);
System.out.println("Cureent Time: " + time);
LocalDate date1 = date.plusYears(1).plusMonths(1).plusWeeks(1).plusDays(1);
System.out.println("Date After adding 1 years, 1 month and 1 days = " + date1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
String formattedDate = date.format(formatter);
System.out.println("Date After fomatting it = " + formattedDate);
LocalDate dateFromString = LocalDate.parse(formattedDate, formatter);
System.out.println("Date Conversion from String = " + formattedDate);
}
}
Output
Cureent Date: 2023-09-22 Cureent Time: 10:11:20.804100 Date After adding 1 years, 1 month and 1 days = 2024-10-30 Date After fomatting it = 22-09-2023 Date Conversion from String = 22-09-2023
9. Define method references in Java. How do they simplify code that uses lambda expressions?
Method references in Java are a feature introduced in Java 8 that allow you to reference methods of classes or objects in a concise way. They serve as a shorthand or more readable syntax for a lambda expression that calls a single method. Method references can be seen as syntactic sugar that simplifies the code, making it clearer and more elegant, especially in contexts where lambda expressions are used simply to call existing methods.
Syntax
There are four types of method references in Java:
- Reference to a Static Method:
- Syntax:
ClassName::methodName - Example:
String::valueOftranslates tox -> String.valueOf(x)
- Syntax:
- Reference to an Instance Method of a Particular Object:
- Syntax:
instance::methodName - Example:
System.out::printlntranslates tox -> System.out.println(x)
- Syntax:
- Reference to an Instance Method of an Arbitrary Object of a Particular Type:
- Syntax:
ClassName::methodName - Example:
String::toLowerCasetranslates tox -> x.toLowerCase()
- Syntax:
- Reference to a Constructor:
- Syntax:
ClassName::new - Example:
ArrayList::newtranslates to() -> new ArrayList()
- Syntax:
Examples
Using a Lambda Expression:
List<String> names = Arrays.asList("John", "Jane", "Doe");
names.forEach(name -> System.out.println(name));Simplified Using a Method Reference:
names.forEach(System.out::println);In the example above, the method reference System.out::println is more clear and equally expressive compared to its lambda expression.
10. What are Lambda Functions?
Lambda functions, introduced in Java 8, are a feature that allows you to express instances of single-method interfaces (functional interfaces) more concisely. They are often referred to as anonymous methods or lambda expressions.
Syntax
The basic syntax of a lambda expression is:
(parameters) -> expressionExample
@FunctionalInterface
interface Greeting {
String sayHello(String name);
}In this example, Greeting is a functional interface with a single abstract method sayHello that takes a String as an argument and returns a String.
Using the Functional Interface with a Lambda Expression
public class Main {
public static void main(String[] args) {
// Implementing the functional interface using a lambda expression
Greeting greeting = name -> "Hello, " + name + "!";
// Calling the method of the functional interface
System.out.println(greeting.sayHello("John"));
}
}Output
Hello, John!
11. Backward Compatibility of Java 1.8.
Backward compatibility is a key feature in Java, ensuring that newer versions of the Java platform remain compatible with earlier versions. This means that code written for an older version of Java should run unchanged on a newer version of the Java runtime environment (JRE).
Key Aspects of Java 8’s Backward Compatibility:
- Binary Compatibility: Java 8 is designed to ensure that compiled Java programs (bytecode) that ran on older versions (like Java 7, Java 6) can run on the Java 8 runtime without modification. This is crucial for not breaking existing applications when upgrading the JRE.
- Source Compatibility: Java 8 aims to ensure that source code written in older Java versions can be compiled on the Java 8 compiler. However, there might be exceptions where deprecated methods are removed or certain behaviors are changed, requiring minor modifications to the source code.
- Behavioral Compatibility: While Java 8 maintains compatibility at the binary and source levels, there might be changes in the JVM’s behavior to improve performance, security, or bug fixes. These changes are generally subtle and unlikely to affect well-written applications but can impact applications relying on unspecified behaviors or using APIs in unintended ways.
12. What java 8 provides for async calls.
Java 8 introduced the CompletableFuture class in the java.util.concurrent package, providing a powerful set of tools for asynchronous programming. This class represents a future result of an asynchronous computation—a placeholder for a result that will be available in the future. CompletableFuture can be manually completed and used to write non-blocking asynchronous code that is composable and easy to parallelize.
13. List convert into Map<empName,empSalary> using java8.
import java.util.*;
import java.util.stream.Collectors;
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
Map<String ,Double> map = employees.stream().collect(Collectors.toMap(Employee::getName, Employee::getSalary));
map.forEach((k,v)->System.out.println(k+" "+v));
}
}14. Sort map based on map value using java8.
import java.util.*;
import java.util.stream.*;
public class LocalDateToStringExample {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("prakash1",4);
map.put("prakash3",2);
map.put("prakash2",5);
map.put("prakash4",6);
Map<String,Integer> sortedMap =
map.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1,e2)->e1,
LinkedHashMap::new
));
sortedMap.forEach((k,v)->System.out.println(k+" "+v));
}
}Output
prakash3 2 prakash1 4 prakash2 5 prakash4 6
15. Difference in Collections.sort and Streams.sorted.
Collections.sort:
Collections.sortis a method available in thejava.util.Collectionsclass.- It is used to sort collections, primarily
Listimplementations, in-place. - The sorting is done based on the natural order (if elements are comparable) or using a provided
Comparator. - It modifies the original collection and does not return a new sorted collection.
Example
List<Integer> numbers = Arrays.asList(3, 1, 2, 4);
Collections.sort(numbers); // Sorts the list in-placeStreams.sorted
sortedis an intermediate operation available in Java Streams.- It returns a new Stream with the elements sorted based on the provided comparator or natural order.
- It does not modify the original Stream or collection.
- It is typically used when you want to obtain a sorted view of the elements without altering the original data.
Example
List<Integer> numbers = Arrays.asList(3, 1, 2, 4);
List<Integer> sortedNumbers = numbers.stream()
.sorted() // Returns a new Stream with sorted elements
.collect(Collectors.toList()); // Collect the sorted elements into a new listIn summary, the main differences are:
Collections.sortmodifies the original collection in-place, whileStreams.sortedreturns a new Stream with sorted elements.Collections.sortis used for sorting collections, whileStreams.sortedis used for obtaining a sorted view of the elements in a Stream.Collections.sortrequires a mutable collection (like aList) as input, whileStreams.sortedcan operate on any streamable data source, including collections.
16. What are default methods? How to call them.
Default methods, also known as defender methods, are a feature introduced in Java 8 to allow interfaces to have method implementations.
Prior to Java 8, interfaces could only declare abstract methods, which meant that any class implementing an interface had to provide concrete implementations for all of its methods. Default methods provide a way to add new methods to existing interfaces without breaking backward compatibility with classes that implement those interfaces.
Example of calling default method
import java.util.*;
interface DefaultImplementation{
default void defaultData(){
System.out.println("Calling default method");
}
}
public class Main implements DefaultImplementation{
public static void main(String[] args) {
Main main = new Main();
main.defaultData();
}
}Output
Calling default method
17. Features of Java 8?
Here the list of Java 8 features
- Lambda Expressions
- Functional Interfaces
- Default Methods
- Method References
- Streams API
- Functional Programming Features
- Optional
- New Date and Time API
- Default Time Zone
- New Collectors
- Parallel Array Sorting
- CompletableFuture
18. What java 8 provides for async calls.
Java 8 provides CompletableFuture class. It simplifies asynchronous programming by providing a way to work with asynchronous operations and compose them using a fluent API.
19. Create Custom Method References.
Here the example of creating custom Method References
@FunctionalInterface
interface MyFunction<T, R> {
R apply(T t);
}
class MathUtils {
public static Integer cube(Integer x) {
return x * x * x;
}
}
public class CustomMethodReferenceExample {
public static void main(String[] args) {
// Create a custom method reference to the cube method in MathUtils
MyFunction<Integer, Integer> cubeFunction = MathUtils::cube;
// Use the custom method reference to cube a number
Integer result = cubeFunction.apply(5);
System.out.println("Cube of 5: " + result); // Output: Cube of 5: 125
}
}Output
Cube of 5: 125
Explanations
- We define a functional interface
MyFunctionwith a single abstract methodapply, which takes a parameter of typeTand returns a result of typeR. - We create a class
MathUtilswith a static methodsquarethat matches the signature of theapplymethod in theMyFunctioninterface. This method squares an integer. - In the
CustomMethodReferenceExampleclass, we create a custom method referencesquareFunctionby referencing thesquaremethod in theMathUtilsclass usingMathUtils::square. - We use the custom method reference
squareFunctionto square the number5, and the result is printed to the console.
20. Importance of @FunctionalInterface annotation.
The @FunctionalInterface annotation in Java serves as a marker that provides important information about an interface. While it is not mandatory to use this annotation when creating functional interfaces.
Here are purpose of using @FunctionalInterface annotations
- Clarity and Intent: The
@FunctionalInterfaceannotation makes the intent of the interface clear to developers. - Compile-Time Checks: When an interface is annotated with
@FunctionalInterface, the Java compiler checks that the interface indeed has only one abstract method. - Readability: Code that uses functional interfaces with
@FunctionalInterfaceannotation is often more readable and self-explanatory.
Example:
import java.util.*;
@FunctionalInterface
interface Operation{
public int operation(int a, int b);
default void display(){
System.out.println("");
}
}
public class Main
{
public static void main(String[] args) {
Operation sum = (a,b)->a+b;
Operation sub = (a,b)->a-b;
System.out.println("sum = " + sum.operation(2,3));
System.out.println("sub = " + sub.operation(6,3));
}
}
Output
sum = 5 sub = 3
21. What are Intermediate operations and terminal operations in Java Streams?
In Java Streams, operations can be categorized into two main types: intermediate operations and terminal operations. These two types of operations serve distinct purposes and are used in stream pipelines to process data.
Intermediate Operations:
Intermediate operations are operations that transform an input stream into another stream.
Some common intermediate operations are
- filter(Predicate<T> predicate)
- map(Function<T, R> mapper)
- flatMap(Function<T, Stream<R>> mapper)
- distinct()
- sorted()
- peek(Consumer<T> action)
Terminal Operations:
Terminal operations are operations that produce a result or a side effect.
Some common terminal operations are
- forEach(Consumer<T> action)
- toArray()
- reduce(identity, BinaryOperator<T> accumulator)
- collect(Collector<T, A, R> collector)
- min(Comparator<T> comparator)
- max(Comparator<T> comparator)
- count()
- anyMatch(Predicate<T> predicate)
- allMatch(Predicate<T> predicate)
- noneMatch(Predicate<T> predicate)
- findFirst()
- findAny()
22. What is difference between findFirst() and findAny() in Java Stream?
findFirst() and findAny() are terminal operations used to retrieve elements from a stream.
findFirst():
- The
findFirst()method returns the first element of the stream. - If the stream is empty, it returns an
Optional.empty(). - When used with parallel streams,
findFirst()will always return the first element according to the encounter order, which may not be the same as the order in which elements were processed in parallel.
Optional<String> firstElement = Stream.of("apple", "banana", "cherry").findFirst();
System.out.println(firstElement.orElse("No elements")); // Output: applefindAny():
- The
findAny()method returns any element from the stream. It does not guarantee any specific order. - If the stream is empty, it returns an
Optional.empty(). - When used with parallel streams,
findAny()is useful for parallel processing because it can return the first element that becomes available, regardless of the encounter order. This can lead to more efficient parallel processing.
Optional<String> anyElement = Stream.of("apple", "banana", "cherry").parallel().findAny();
System.out.println(anyElement.orElse("No elements")); // Output: (Output may vary)In Summary
- If you need to find any element from a stream and don’t care about order or parallel processing, you can use
findAny(). - If you specifically want the first element based on encounter order (or if order matters), use
findFirst().
23. Improvements in Date Function in Java 8.
Java 8 introduced a new Date-Time API under the java.time package, addressing many of the shortcomings and design flaws of the old date and time classes (java.util.Date and java.util.Calendar). This new API, inspired by the Joda-Time library, provides a comprehensive, immutable, and fluent framework for working with dates, times, durations, and periods, making date and time manipulation in Java more intuitive and less error-prone.
Key Improvements and Features:
- Immutability: The new date-time classes are immutable, meaning that once an instance is created, it cannot be modified. This makes the new API thread-safe and avoids the mutability-related issues of the old classes.
- Clarity and Fluency: The API has a clear and fluent interface, making code involving date and time operations more readable and easier to understand. Operations on dates and times return new instances, enabling method chaining.
- Separation of Concerns: The API clearly separates different aspects of date-time manipulation:
- Machine Time: Represented by
Instant, it’s time measured from the Unix epoch (January 1, 1970, UTC), useful for timestamping and calculations. - Human Time: Classes like
LocalDate,LocalTime, andLocalDateTimerepresent date, time, and datetime without a timezone, suitable for calendar systems. - Zoned Time:
ZonedDateTimeandOffsetDateTimeare for working with date and time with a specific timezone or offset from UTC, addressing the complexities of global timekeeping.
- Machine Time: Represented by
- Additional Time Units and Granularity: The API supports additional temporal units (like
ChronoUnit) and provides classes for periods (Period) and durations (Duration), offering finer granularity and more flexibility in temporal operations. - Adjusters and Temporal Queries: Through
TemporalAdjusterandTemporalQuery, the API allows powerful and flexible transformations and querying of date-time values. This includes finding the first day of the month, next Tuesday, and more, with both built-in and custom adjusters. - Backward Compatibility: Classes like
DateandCalendarcan be converted to the new format (and vice versa), facilitating the migration of existing code to the new API. - Time Zone and Offset Handling: Improved handling of time zones and offsets with classes like
ZoneId,ZoneOffset, andZonedDateTime, making it easier to develop applications that require precise control over time zones. - Formatting and Parsing: The
DateTimeFormatterclass provides a comprehensive way to format and parse date-time objects, supporting both standard ISO formats and custom patterns.
24. Difference in map and flatmap.
In Java Streams, both map and flatMap are intermediate operations used to transform elements in a stream.
map() :
- The
mapoperation is used to transform each element of the stream into another element using a given mapping function. - The result of
mapis a one-to-one transformation. For each input element, there is exactly one corresponding output element. - If the mapping function returns a stream, the result will be a stream of streams (a nested structure).
List<String> words = Arrays.asList("hello", "world");
List<Integer> wordLengths = words.stream()
.map(String::length) // Transforms each string into its length
.collect(Collectors.toList()); // Result: [5, 5]flatMap() :
- The
flatMapoperation is used when the mapping function returns a stream or a collection for each element, and you want to flatten the resulting nested streams or collections into a single stream of elements. - It not only transforms each element but also flattens the intermediate streams or collections into a single stream of elements.
flatMapis useful when you want to “flatten” a nested structure or combine multiple streams into one.
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flattenedList = listOfLists.stream()
.flatMap(Collection::stream) // Flattens the nested lists
.collect(Collectors.toList()); // Result: [1, 2, 3, 4, 5, 6]
In summary:
mapis used for one-to-one transformations of elements in a stream.flatMapis used to handle cases where the mapping function produces multiple values per element and you want to flatten these values into a single stream.
25. Can a functional interface be implemented by another Functional interface?
Yes, a functional interface in Java can extend another functional interface as long as it does not introduce more than one abstract method in total. The key characteristic of a functional interface is that it must have exactly one abstract method (though it can have multiple default or static methods). This rule allows a functional interface to be used as the target type for lambda expressions and method references, which are central to functional programming in Java.
Example
@FunctionalInterface
interface MyFunctionalInterface {
void abstractMethod();
default void defaultMethod() {
System.out.println("This is a default method.");
}
static void staticMethod() {
System.out.println("This is a static method.");
}
}
@FunctionalInterface
interface ExtendedFunctionalInterface extends MyFunctionalInterface {
// No new abstract methods are introduced, so it's still a functional interface.
@Override
default void defaultMethod() {
// Overriding the default method from MyFunctionalInterface
System.out.println("This is an overridden default method.");
}
static void anotherStaticMethod() {
System.out.println("This is another static method.");
}
}Interview Questions on Collection Framework
Check out our “Collection Framework” section to get ready for Java interviews that ask about managing groups of objects. This part of our website is full of questions about the Collection Framework in Java, which helps programmers organize and handle different sets of data efficiently. You’ll get questions on basic structures like lists and sets, and more complicated ones like maps and queues. This section is awesome for beginners, freshers and experienced Java developer. Those who already know some Java, this section will also helpful for them. Please go through the below questions and practice them. You can confidently answer questions about the Collection Framework in your interviews.
1. What is Collection in Java?
In Java, a Collection is an interface, is part of the Java Collections Framework and serves as the root interface for several collection types. It represents a group or collection of objects. Collections provide a framework for storing, retrieving, manipulating, and processing a group of objects as a single unit.
Java Collections Framework (JCF) is a set of classes and interfaces that implement various types of collections and algorithms to work with them.
Collections Framework Interfaces:
- List: Ordered collection with duplicates allowed.
- Set: Unordered collection with no duplicates.
- Map: Key-value pairs where each key maps to a unique value.
- Queue: Ordered collection typically used for managing elements in a FIFO (First-In, First-Out) manner.
2. Collection vs Collections.
Collection (Interface):
Collectionis an interface in the Java Collections Framework (JCF) that represents a group of objects or elements. It’s the root interface of many other collection-related interfaces, includingList,Set,Queue, andMap.- This interface defines common methods for working with collections, such as adding elements, removing elements, checking for containment, and iterating over elements.
- It’s part of the
java.utilpackage and provides a unified interface for working with different types of collections.
Collection<String> myCollection = new ArrayList<>();
myCollection.add("Apple");
myCollection.add("Banana");Collections (Class):
Collectionsis a utility class in Java’s standard library (thejava.utilpackage) that provides various static methods for performing operations on collections. It does not represent a collection itself but offers utility methods for manipulating and working with collections.- Common operations performed by
Collectionsmethods include sorting, shuffling, searching, synchronization, and creating unmodifiable views of collections. - The
Collectionsclass cannot be instantiated because it consists entirely of static methods.
List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(1);
numbers.add(2);
Collections.sort(numbers); // Sorts the list in ascending orderIn summary:
Collectionis an interface that defines the contract for collections in Java. It represents a group of elements and provides common methods for working with collections.Collectionsis a utility class that provides static methods for performing operations on collections, making it easier to work with collections in various ways, including sorting, shuffling, and synchronization.
3. Tell me core interfaces in the Java Collections Framework?
Java Collections Framework (JCF) provides several core interfaces
- Collection:
java.util.Collectionis the root interface of the Java Collections Framework. - List:
java.util.Listis an ordered collection. - Set:
java.util.Setis an unordered collection. - Queue:
java.util.Queueis an interface follow the FIFO (First-In, First-Out) order. - Map:
java.util.Mapis an interface. - Deque:
java.util.Dequeis a double-ended queue. - SortedSet:
java.util.SortedSetis a subinterface ofSet. - SortedMap:
java.util.SortedMapis a subinterface ofMap.
4. What is the difference between List, Set, and Map interfaces.
In Java, List, Set, and Map are three different interfaces provided by the Java Collections Framework, and they serve different purposes and have distinct characteristics:
List:
- Ordered Collection: A
Listis an ordered collection of elements where each element has an index based on its position in the list. You can access elements in aListusing their index. - Duplicates Allowed: A
Listallows duplicate elements, meaning the same element can appear multiple times in the list. - Common Implementations: Common implementations of the
Listinterface includeArrayList,LinkedList, andVector.
List<String> myList = new ArrayList<>();
myList.add("Alice");
myList.add("Bob");
myList.add("Alice"); // Duplicates are allowedSet:
- Unordered Collection: A
Setis an unordered collection of unique elements. It does not allow duplicate elements. - No Indexing: Unlike a
List, you cannot access elements in aSetby index because it does not maintain an order of elements. - Common Implementations: Common implementations of the
Setinterface includeHashSet,LinkedHashSet, andTreeSet.
Set<String> mySet = new HashSet<>();
mySet.add("Alice");
mySet.add("Bob");
mySet.add("Alice"); // Duplicates are automatically removedMap:
- Key-Value Pairs: A
Mapis a collection that stores key-value pairs. Each key in aMapis associated with a unique value. Keys are used to access values. - No Duplicate Keys: While values can be duplicated, keys in a
Mapare unique. If you try to put a value with an existing key, it will overwrite the previous value. - Common Implementations: Common implementations of the
Mapinterface includeHashMap,LinkedHashMap, andTreeMap.
Map<String, Integer> myMap = new HashMap<>();
myMap.put("Alice", 25);
myMap.put("Bob", 30);
myMap.put("Alice", 28); // The value for "Alice" is updated5. Difference between ArrayList and LinkedList?
| Characteristic | ArrayList | LinkedList |
|---|---|---|
| Data Structure | Dynamic array (resizable) | Doubly-linked list |
| Insertion/Deletion | Slower for insertions/removals | Faster for insertions/removals |
| Random Access | Fast (constant-time) | Slower (O(n)) |
| Memory Overhead | Lower memory overhead | Higher memory overhead |
| Iteration | Faster for sequential access | Slower for sequential access |
| Search | Faster for searching (O(n)) | Slower for searching (O(n)) |
| Ordered Collection | Yes | Yes |
| Duplicates | Allowed | Allowed |
| Use Cases | Lists with infrequent changes | Frequent insertions/removals |
| Notable Methods | add, remove, get | add, remove, get, offer, poll, peek |
6. Comparable vs Comparator interfaces.
In Java, both the Comparable and Comparator interfaces are used for sorting objects, but they serve different purposes and provide distinct mechanisms for sorting.
Comparable Interface:
- The
Comparableinterface enables the objects of a class to be compared to each other based on their properties. Comparableinterface has one method,compareTo(Object obj).- This method returns an integer value:
- Negative value: If the current object is less than the object being compared.
- Zero: If the current object is equal to the object being compared.
- Positive value: If the current object is greater than the object being compared.
import java.util.*;
class Employee implements Comparable<Employee>{
int age;
String name;
Employee(int age, String name){
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public int compareTo(Employee emp){
return Integer.compare(this.age, emp.age);
}
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age + '}';
}
}
public class Main
{
public static void main(String[] args) {
List<Employee> emp = Arrays.asList(new Employee(23,"prakash1"), new Employee(43,"prakash2"), new Employee(13,"prakash3"));
Collections.sort(emp);
emp.stream().forEach(s->System.out.println(s.getName()));
}
}Output
prakash3
prakash1
prakash2
Comparator Interface
- The
Comparatorinterface is used to define custom comparison rules for objects of a class. - The
Comparatorinterface has one method,compare(Object obj1, Object obj2). This method returns an integer value, similar to thecompareTomethod inComparable.
import java.util.*;
class Employee{
int age;
String name;
Employee(int age, String name){
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age + '}';
}
}
public class Main
{
public static void main(String[] args) {
List<Employee> emp = Arrays.asList(new Employee(23,"manish"), new Employee(43,"akash"), new Employee(13,"prakash"));
Comparator<Employee> nameComparator = Comparator.comparing(Employee::getName);
emp.sort(nameComparator);
emp.stream().forEach(s->System.out.println(s.getName()));
}
}Output
akash manish prakash
7. Difference between HashSet and TreeSet.
| Characteristic | HashSet | TreeSet |
|---|---|---|
| Data Structure | Hash table | Red-Black Tree |
| Ordering | Unordered (no specific order) | Ordered (sorted in natural or specified order) |
| Duplicates | Not allowed (unique elements) | Not allowed (unique elements) |
| Null Values | Allows a single null element | Does not allow null elements |
| Insertion/Deletion | Generally faster for these operations | Slower for these operations |
| Iteration | Order of elements not guaranteed | Elements are in sorted order |
| Search | Fast (average O(1) time complexity for contains, add, remove) | Fast (O(log n) time complexity for contains, add, remove) |
| Use Cases | When order and duplicates don’t matter, and you need fast insertions and lookups | When elements should be in sorted order and duplicates are not allowed |
| Notable Methods | add, remove, contains | add, remove, contains, first, last, headSet, tailSet, subSet |
Key points to consider:
HashSetis implemented using a hash table, which provides fast average-case performance for insertion, deletion, and searching.TreeSetis implemented using a self-balancing binary search tree (Red-Black Tree), which maintains elements in sorted order. This makes it suitable when you need sorted elements.- Both
HashSetandTreeSetdo not allow duplicate elements. HashSetallows a singlenullelement, whileTreeSetdoes not allow anynullelements.- Iteration order in
HashSetis not guaranteed and can change over time due to resizing. InTreeSet, elements are always ordered (sorted).
8. How HashMap work internally?
Internally, a HashMap in Java operates based on the principles of hashing, where it stores its elements (key-value pairs) in buckets using an array of nodes. Each node is essentially a linked list to handle collisions, although Java 8 and later can convert these linked lists into balanced trees (red-black trees) under certain conditions for efficiency. Here’s a step-by-step breakdown of how HashMap works internally:
Initial Setup
- When a
HashMapis instantiated, it initializes an array to store the entries. Each entry in the array is referred to as a bucket, and each bucket can hold zero or more entries (in the form of a linked list or a tree).
Adding Elements (put(K key, V value) Method)
- Hashing: When the
put()method is invoked,HashMapcomputes the hash code of the key using itshashCode()method. This raw hash code is then processed to reduce collision risk and to distribute keys more evenly across the buckets. - Index Calculation: The processed hash code is used to calculate the index in the array (bucket location) where the key-value pair should be stored. The formula generally involves using the hash code’s value modulo the array’s length.
- Collision Handling: If the calculated bucket index is already occupied by one or more entries, this constitutes a collision. Collisions are handled by linking new entries to existing ones within the same bucket, forming a linked list or a tree (for Java 8 and later, if the number of entries in a bucket reaches a certain threshold).
- Replacing Existing Entries: If a new entry’s key equals an existing entry’s key (as determined by the
equals()method), the new entry replaces the old one, and the old value is returned by theput()method. - Rehashing/Resizing: If the number of elements exceeds the capacity of the
HashMapmultiplied by the load factor (a measure that determines how full theHashMapcan get before it’s resized), theHashMapis resized. Resizing involves creating a new, larger array and rehashing all existing entries to distribute them across the new array.
Retrieving Elements (get(Object key) Method)
- Hashing: Compute the hash code of the key and determine the array index.
- Search: Traverse the linked list or tree at the calculated index. Use the
equals()method to identify the correct entry. - Return Value: Return the value associated with the found entry. If no entry is found, return
null.
9. Explain equals and hashCode methods in Java collections. Also know as equals and hashcode contract.
In Java, the equals() and hashCode() methods are used for comparing and hashing objects, respectively, which are crucial for various Java collections like HashSet, HashMap, and Hashtable. These methods work together to ensure proper functioning of collections that rely on the concept of equality and hashing.
Example equal() method
- The
equals()method is used to compare two objects for equality. - It is defined in the
java.lang.Objectclass and can be overridden in user-defined classes to provide custom equality checks. - The general contract of the
equals()method is to compare the contents or attributes of objects to determine if they are equal. - When using collections like
HashSetorHashMap, theequals()method is used to determine if two objects are considered equal.
class Person {
private String name;
private int age;
// Constructor, getters, and setters...
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
}Example hashCode() method
- The
hashCode()method is used to compute a hash code (an integer value) for an object.
@Override
public int hashCode() {
return Objects.hash(name, age);
}Some guidelines for implementing equals() and hashCode()
- If you override
equals(), always overridehashCode()to ensure consistency. - Ensure that the fields used for equality checks in
equals()are the same fields used inhashCode(). - Use the
Objectsclass or other utilities to simplifyequals()andhashCode()implementations.
10. Explain CopyOnWriteArrayList and ConcurrentHashMap.
CopyOnWriteArrayList and ConcurrentHashMap are two thread-safe collections provided by Java to support concurrent access by multiple threads without the need for explicit synchronization.
CopyOnWriteArrayList
CopyOnWriteArrayListis a thread-safe variant ofArrayList.- It ensures thread safety by creating a new copy of the underlying array every time an element is added, modified, or removed.
- Reads (such as iteration) can be performed concurrently without blocking or locking.
Example
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// Add elements
list.add("Alice");
list.add("Bob");
list.add("Charlie");
// Iteration is thread-safe
for (String name : list) {
System.out.println(name);
}
}
}Output
Alice Bob Charlie
ConcurrentHashMap
ConcurrentHashMapis a thread-safe implementation of theMapinterface.- It is designed to support concurrent reads and writes efficiently.
- Reads are typically non-blocking and do not require synchronization, allowing multiple threads to read concurrently.
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Put key-value pairs
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);
// Concurrent read
int age = map.get("Alice");
System.out.println("Alice's age: " + age);
}
}Output
Alice's age: 30
11. What is synchronized collections?
Synchronized collections in Java means that collections provide the thread safety. It means concurrent reading and writing by multiple threads.
Thread safe synchronized collections are ConcurrentHashMap and CopyOnWriteArrayList.
12. Concept of immutability in Java collections.
Immutability in Java collections refers to the idea that once a collection is created, its content cannot be changed — no elements can be added, removed, or modified. Immutable collections are inherently thread-safe, as their immutability guarantees that concurrent access by multiple threads will not lead to inconsistent states or require synchronization.
As of Java 9 and later, Java provides straightforward ways to create immutable collections using the static factory methods of and copyOf in the List, Set, and Map interfaces.
Here’s how you can use them:
Immutable List
List<String> immutableList = List.of("Java", "Kotlin", "Scala");Immutable Set
Set<String> immutableSet = Set.of("Java", "Kotlin", "Scala");Immutable Map
Map<String, Integer> immutableMap = Map.of("Java", 24, "Kotlin", 6, "Scala", 16);Before Java 9
Prior to Java 9, immutable collections were typically created by wrapping a collection with the corresponding Collections.unmodifiableXXX method, which returns an unmodifiable view of the specified collection. However, these collections are not truly immutable, as changes to the original collection will affect the unmodifiable view.
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Kotlin");
List<String> unmodifiableList = Collections.unmodifiableList(list);
// unmodifiableList is an unmodifiable view of list
// Any attempt to modify unmodifiableList directly throws UnsupportedOperationException13. Differences between ArrayList and Vector?
| Characteristic | ArrayList | Vector |
|---|---|---|
| Synchronization | Not synchronized by default | Synchronized by default |
| Thread Safety | Not thread-safe | Thread-safe |
| Performance | Generally faster due to lack of synchronization | Slower due to synchronization |
| Growth Mechanism | Grows by 50% of current size when needed | Grows by doubling its size when needed |
| Legacy | Introduced in Java 2 (JDK 1.2) | Introduced in Java 1.0 (legacy) |
| Use Cases | When thread safety is not a concern and performance is critical | When thread safety is required or when working with legacy code |
| Methods Removed | None | addElement, removeElement, clone, etc. (legacy methods) |
Key points
ArrayListis not synchronized by default, meaning that it is not thread-safe.Vector, on the other hand, is synchronized by default. This ensures that all its methods are thread-safe.
14. Differences between HashSet and LinkedHashSet.
| Characteristic | HashSet | LinkedHashSet |
|---|---|---|
| Ordering | Unordered (no specific order) | Ordered (insertion order) |
| Duplicates | Not allowed (unique elements) | Not allowed (unique elements) |
| Null Values | Allows a single null element | Allows a single null element |
| Iteration Order | Order of elements not guaranteed | Elements are in insertion order |
| Performance | Generally faster for lookups | Slower for lookups compared to HashSet |
| Use Cases | When order and duplicates don’t matter, and you need fast lookups | When order of elements should match insertion order and duplicates are not allowed |
Key points:
HashSetis implemented using a hash table, which provides fast average-case performance for lookups (contains) but does not guarantee any specific order of elements.maintains the insertion order of elements, which means the order in which elements were added to the set is preserved. This ordering is determined by a linked list of elements.
LinkedHashSet
15. Concept of a concurrent collection.
A concurrent collection, often referred to as a “concurrent data structure” or “thread-safe collection. It is a special type of collection in Java designed to support concurrent access by multiple threads while maintaining data consistency and preventing race conditions.
In other words, concurrent collections provide a safe way for multiple threads to read, write, and modify data simultaneously without the risk of data corruption or other concurrency-related issues.
16. Thread Safe Collections in java.
In Java, thread-safe collections are designed to ensure that the collection can be safely operated on by multiple threads at the same time without causing any inconsistencies or corrupting the collection’s state. Java provides several options for working with thread-safe collections, mainly through the java.util.concurrent package, introduced in Java 5.
Concurrent Collections
The java.util.concurrent package provides several thread-safe collections designed for higher concurrency:
ConcurrentHashMap: A highly concurrent, thread-safe implementation of a hash map.
Map<String, String> concurrentMap = new ConcurrentHashMap<>();ConcurrentLinkedQueue: An efficient, thread-safe queue based on linked nodes.
Queue<String> concurrentQueue = new ConcurrentLinkedQueue<>();ConcurrentSkipListMap and ConcurrentSkipListSet: Thread-safe sorted map and set implementations.
NavigableSet<String> skipListSet = new ConcurrentSkipListSet<>();
NavigableMap<String, String> skipListMap = new ConcurrentSkipListMap<>();CopyOnWriteArrayList and CopyOnWriteArraySet: These are thread-safe variants of ArrayList and HashSet that use a copy-on-write mechanism. They are very efficient if you have few mutation operations but many iterations or reads.
List<String> cowList = new CopyOnWriteArrayList<>();
Set<String> cowSet = new CopyOnWriteArraySet<>();Blocking Queues: The java.util.concurrent package includes several blocking queue implementations (like ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, etc.), which support operations that wait for the queue to become non-empty when retrieving elements and wait for space to become available in the queue when storing elements.
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1024);17. Advantages and Disadvantages of using a ConcurrentHashMap over a synchronized HashMap in a multi-threaded environment?
ConcurrentHashMap and a synchronized HashMap (Collections.synchronizedMap(new HashMap<>())) are two ways to achieve thread safety with map operations in Java, but they have different characteristics that make them suitable for different scenarios.
Advantages of ConcurrentHashMap over Synchronized HashMap
- Higher Concurrency:
ConcurrentHashMapallows multiple readers and a limited number of writers to access the map concurrently without blocking. This leads to higher throughput and better scalability in environments with many threads accessing the map simultaneously. - Fine-grained Locking: Instead of locking the whole map like a synchronized
HashMap,ConcurrentHashMapuses a finer-grained locking mechanism (segment locking or lock striping in earlier versions and a sophisticated version of it usingsynchronizedand volatile variables in Java 8 and later). This means only a part of the map is locked for writing, reducing contention among threads. - No Need for External Synchronization: With
ConcurrentHashMap, no external synchronization is required for performing safe operations, as the implementation takes care of all concurrency issues internally. In contrast, when using a synchronizedHashMap, you must ensure that all iterations over the map are also synchronized on the same monitor. - Fail-Safe Iterators: The iterators returned by
ConcurrentHashMapare fail-safe and do not throwConcurrentModificationExceptionif the map is modified while iterating. This is achieved without locking the whole map and provides a snapshot-style iterator that works on a copy of the entries in the map at the time of the iterator’s creation.
Disadvantages of ConcurrentHashMap compared to Synchronized HashMap
- Memory Overhead:
ConcurrentHashMapmight have higher memory overhead compared to aHashMapdue to its internal structures for ensuring thread safety. - Slightly Lower Single-thread Performance: For single-threaded scenarios or scenarios with minimal contention, a synchronized
HashMapmight perform slightly better due to the simpler data structure and lack of concurrency control overhead. - Null Restrictions:
ConcurrentHashMapdoes not allow null keys or values. While this is often a benefit for avoidingNullPointerException, in some scenarios, the ability to store nulls (which a synchronizedHashMappermits) might be required. - Consistency Model: While
ConcurrentHashMapprovides thread safety, it does not lock the whole map for reads and writes, which means the view of the map might not always be up-to-date or consistent across all threads at any given moment. In contrast, operations on a synchronizedHashMapare fully synchronized and thus always provide a consistent view to the thread performing the operations, at the cost of reduced concurrency.
18. Difference between a List and a Set, and when would you choose one over the other?
| Characteristic | List | Set |
|---|---|---|
| Ordering | Ordered (elements have an index) | Unordered (no specific order) |
| Duplicates | Allows duplicates (same element can appear multiple times) | Does not allow duplicates (unique elements) |
| Null Values | Allows multiple null elements | Typically allows a single null element (implementation-specific) |
| Use Cases | – When you need to maintain elements in a specific order. | – When you need to ensure uniqueness of elements and order doesn’t matter. |
| – When you require duplicates to be stored. | – When you need to perform set operations like union, intersection, and difference. | |
| – When you need to access elements by index. | – When you want to avoid storing duplicate values. | |
| Examples | ArrayList, LinkedList,Vector, CopyOnWriteArrayList | HashSet, TreeSet, LinkedHashSet |
When to Choose List:
- Order Matters: Choose a
Listwhen you need to maintain elements in a specific order, and the order of insertion or some other criterion is essential. - Duplicates Allowed: If your data can contain duplicate values and you want to preserve all occurrences of the same element, a
Listallows duplicates. - Indexed Access: When you need to access elements by index (e.g.,
list.get(index)), aListprovides this feature. - Data Can Be Ordered: When your data naturally has an order, such as a list of tasks to be executed sequentially or a playlist of songs, a
Listis a suitable choice.
When to Choose Set:
- Uniqueness Required: Use a
Setwhen you need to ensure that the collection contains unique elements, and you want to avoid storing duplicate values. - Order Doesn’t Matter: When the order of elements is not important to your use case, and you prioritize uniqueness, a
Setis a better choice. - Set Operations: If you need to perform set operations like union, intersection, and difference between collections,
Setinterfaces (HashSet,TreeSet, etc.) provide these operations. - Fast Lookup: When you require fast lookup of elements and don’t need to retrieve elements by their position or index,
Setimplementations, especiallyHashSet, offer efficient retrieval.
19. Difference between a shallow copy and a deep copy of a collection?
Shallow Copy:
- A shallow copy of a collection creates a new collection, but it does not duplicate the objects contained within the original collection. Instead, it copies references to the same objects.
- The copied collection will contain references to the same objects as the original collection. Therefore, changes made to the objects in the copied collection will be reflected in the original collection and vice versa.
- Shallow copies are relatively faster and consume less memory compared to deep copies, as they don’t duplicate object data.
Example of shallow copy
import java.util.ArrayList;
import java.util.List;
public class ShallowCopyExample {
public static void main(String[] args) {
List<StringBuilder> originalList = new ArrayList<>();
originalList.add(new StringBuilder("Interview"));
originalList.add(new StringBuilder("Expert"));
// Creating a shallow copy
List<StringBuilder> shallowCopy = new ArrayList<>(originalList);
// Modifying an object in the shallow copy
StringBuilder modified = shallowCopy.get(0);
modified.append(" copy");
System.out.println("Original List: " + originalList);
System.out.println("Shallow Copy: " + shallowCopy);
}
}Output
Original List: [Interview copy, Expert] Shallow Copy: [Interview copy, Expert]
Deep Copy:
- A deep copy of a collection creates a new collection and also duplicates the objects contained within the original collection. It recursively copies all the elements, including their internal state.
- The copied collection will have new instances of the objects, which are entirely independent of the objects in the original collection. Changes made to the objects in the copied collection do not affect the original collection, and vice versa.
- Deep copies are generally slower and consume more memory compared to shallow copies because they involve creating new object instances.
Example of deep copy
import java.util.ArrayList;
import java.util.List;
public class DeepCopyExample {
public static void main(String[] args) {
List<StringBuilder> originalList = new ArrayList<>();
originalList.add(new StringBuilder("Interview"));
originalList.add(new StringBuilder("Expert"));
// Creating a deep copy
List<StringBuilder> deepCopy = new ArrayList<>();
for (StringBuilder sb : originalList) {
deepCopy.add(new StringBuilder(sb.toString())); // Create a new instance
}
// Modifying an object in the deep copy
StringBuilder modified = deepCopy.get(0);
modified.append(" modified");
System.out.println("Original List: " + originalList);
System.out.println("Deep Copy: " + deepCopy);
}
}
Output
Original List: [Interview, Expert] Deep Copy: [Interview modified, Expert]
20. Explain fail-fast and fail-safe iterators in Java collections.
Fail-Fast and Fail-Safe are two different strategies in Java collections to handle concurrent modification while iterating over a collection.
Fail-Fast Iterators:
- A fail-fast iterator immediately throws a
ConcurrentModificationExceptionif it detects that the collection has been modified during the iteration (e.g., elements added, removed, or modified). - Fail-fast iterators are typically used in collections like
ArrayList,HashMap, andHashSet. They are suitable when you want to detect and respond to concurrent modifications during iteration, ensuring that the iterator state remains consistent with the collection.
Example of Fail-Fast Iterator
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
public class FailFastIteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
// Fail-Fast Iterator
Iterator<String> failFastIterator = list.iterator();
while (failFastIterator.hasNext()) {
String element = failFastIterator.next();
System.out.println(element);
list.add("four");
}
}
}Output
one
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at FailFastIteratorExample.main(FailFastIteratorExample.java:17)
Fail-Safe Iterators:
- A fail-safe iterator operates on a cloned or snapshot copy of the collection, allowing the original collection to be modified without affecting the ongoing iteration.
- Fail-safe iterators are commonly used in concurrent collections like
ConcurrentHashMapand concurrent data structures. They are suitable when you want to ensure that concurrent modifications to the collection do not interfere with the ongoing iteration.
Example 1
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.*;
import java.util.List;
public class FailSafeIteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
// Fail-Safe Iterator
Iterator<String> failSafeIterator = new ArrayList<>(list).iterator();
int count=0;
while (failSafeIterator.hasNext()) {
String element = failSafeIterator.next();
System.out.println(element);
list.add("five");
}
}
}Output
one two three
Example 2
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
class FailSafeExample {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("A");
list.add("B");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String element = itr.next();
if(element.equals("A")){
list.add("AA");
}
}
System.out.println(list);
}
}Output
[A, B, C, A, B, AA, AA]
21. What is PriorityQueue and BlockingQueue?
PriorityQueue and BlockingQueue are two specialized implementations of the Java Queue interface, each serving a specific purpose in different scenarios.
PriorityQueue:
PriorityQueueis an unbounded, priority-ordered queue that allows you to store elements with associated priorities and retrieve them in ascending or descending order of priority.- Elements in a
PriorityQueueare ordered based on their natural ordering or according to a specified comparator. - It is used in Task scheduling application where the tasks or jobs are managed based on their priorities.
- It can be used to implement algorithms (like Dijkstra’s algorithm) that require processing elements with the lowest or highest priority first.
Priority Queue Example:
import java.util.Comparator;
class PriorityElement {
private String data;
private int priority;
public PriorityElement(String data, int priority) {
this.data = data;
this.priority = priority;
}
public String getData() {
return data;
}
public int getPriority() {
return priority;
}
@Override
public String toString() {
return "PriorityElement{" +
"data='" + data + '\'' +
", priority=" + priority +
'}';
}
}
public class PriorityQueueWithPriorityExample {
public static void main(String[] args) {
// Create a PriorityQueue with a custom Comparator to prioritize elements by their priority
PriorityQueue<PriorityElement> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(PriorityElement::getPriority));
// Add elements with associated priorities
priorityQueue.add(new PriorityElement("Task A", 2));
priorityQueue.add(new PriorityElement("Task B", 1));
priorityQueue.add(new PriorityElement("Task C", 3));
// Poll elements in ascending order of priority
while (!priorityQueue.isEmpty()) {
PriorityElement element = priorityQueue.poll();
System.out.println("Processing: " + element);
}
}
}Output
Processing: PriorityElement{data='Task B', priority=1}
Processing: PriorityElement{data='Task A', priority=2}
Processing: PriorityElement{data='Task C', priority=3}
BlockingQueue:
BlockingQueueis a thread-safe queue that provides blocking operations for managing data between producer and consumer threads. It is designed to handle scenarios where one thread produces data, and another thread consumes it.BlockingQueueoffers blocking operations likeput(blocks if the queue is full),take(blocks if the queue is empty), and others.- It is commonly used to implement producer-consumer patterns where data is produced by one thread and consumed by another.
- A
BlockingQueuecan be used to manage a pool of worker threads that pick up tasks from the queue for execution.
22. Difference in ArrayList and Hashset.
ArrayList and HashSet are both data structures used for storing and manipulating collections of objects in Java.
| Characteristic | ArrayList | HashSet |
|---|---|---|
| Implementation | Implemented as a dynamic array that can grow in size as needed. | Implemented as a hash table (using HashMap internally) to provide fast retrieval and uniqueness. |
| Ordering | Ordered (elements have an index, maintain the insertion order). | Unordered (no specific order guaranteed). |
| Duplicates | Allows duplicates (same element can appear multiple times). | Does not allow duplicates (unique elements only). |
| Null Values | Allows multiple null elements. | Typically allows a single null element (implementation-specific). |
| Access Time | Provides fast O(1) access by index. | Provides fast O(1) access by hash code. |
| Search Time | Linear O(n) time for searching by value. | O(1) average case time for searching by value. |
| Use Cases | When you need an ordered collection of elements and duplicates are allowed. | When you need a unique collection of elements, and order doesn’t matter. |
| Example | java ArrayList<String> list = new ArrayList<>(); | java HashSet<String> set = new HashSet<>(); |
23. Default size of HashSet.
In Java 8 and later, HashSet uses the following default values:
- Default Initial Capacity: 16
- Default Load Factor: 0.75
These values are defined in the HashSet class as constants:
DEFAULT_INITIAL_CAPACITY: This constant represents the default initial capacity, which is 16.DEFAULT_LOAD_FACTOR: This constant represents the default load factor, which is 0.75.
24. HashMap and ConcurrentHashMap?
| Characteristic | HashMap | ConcurrentHashMap |
|---|---|---|
| Thread Safety | Not thread-safe (not safe for concurrent use). | Thread-safe (supports concurrent access). |
| Concurrent Writes | Multiple threads can lead to unpredictable results or data corruption if not synchronized externally. | Supports concurrent writes without external synchronization. |
| Performance | Generally faster for single-threaded access. | Performance may be slightly slower for single-threaded access due to added synchronization. |
| Scalability | May not scale well with a high number of threads concurrently writing. | Scales well with a high number of threads writing concurrently. |
| Iterating | Safe for iterating when no structural modification is performed during iteration. | Supports safe and non-blocking iteration. |
| Null Values | Allows a single null key and multiple null values. | Allows a single null key and null values. |
| Use Cases | Suitable for single-threaded scenarios or when external synchronization is provided. | Ideal for concurrent scenarios where multiple threads read and write concurrently. |
| Example | java HashMap<String, Integer> map = new HashMap<>(); | java ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>(); |
25. reduce() function in Streams.
reduce() function takes a sequence of elements from a stream, combines them using a specific operation (like adding them), and returns one final result.
Example : Sum of Numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
int sum = numbers.stream().reduce(0, (total, number) -> total + number);
System.out.println(sum); // Output: 10The lambda expression (total, number) -> total + number tells how to combine two numbers (add them in this case).
26. Working of HashSet and HashMap.
HashSet and HashMap are two fundamental data structures in Java’s Collection Framework, both backed by a hash table, which is a data structure that offers efficient operations for storing, retrieving, and managing data. Here’s how they work:
HashMap
A HashMap in Java is a map-based collection that stores key-value pairs. It uses hashing to determine where to store each key-value pair internally.
- Hashing: When a key-value pair is added to a
HashMap, the key’shashCode()method is called to compute a hash code, which is then used to find a bucket (or index) where this pair should be placed. If two keys produce the same hash code or different hash codes that fall into the same bucket (a situation known as a collision),HashMapuses a linked list or a tree (since Java 8) to store these entries within the same bucket. - Retrieval: To retrieve a value,
HashMapcomputes the hash code of the key, finds the correct bucket, and then searches through the list or tree in that bucket to find the matching key. Since Java 8, if a bucket has too many entries (exceeding a certain threshold), the linked list is transformed into a balanced tree (red-black tree) to improve search time from O(n) to O(log n). - Key Uniqueness: Each key in a
HashMapmust be unique. If you insert a key-value pair with a key that already exists in the map, the new value replaces the old value associated with that key. - Null Values:
HashMapallows one null key and multiple null values.
HashSet
A HashSet is a collection that contains no duplicate elements. Internally, it uses a HashMap to store its elements.
- Storage: When an element is added to a
HashSet, the element itself is used as a key in the backingHashMap, and a single shared dummy object is used as the value for all entries. This way,HashSetleveragesHashMap‘s key uniqueness to ensure that no duplicate elements are stored. - Hashing and Uniqueness: Just like
HashMap,HashSetrelies on thehashCode()andequals()methods to determine if an element is already present. If an element’s hash code matches the hash code of any existing element,equals()is called to check for equality. Ifequals()returnstrue, the new element is not added, ensuring uniqueness. - Null Elements:
HashSetallows null elements since its backingHashMapallows one null key.
27. Where is ArrayList and LinkedList preferred?
ArrayList and LinkedList are two commonly used implementations of the List interface in Java’s Collection framework, each with its own set of performance characteristics. Choosing between them depends on the specific requirements of your application, particularly regarding how frequently you perform certain operations like addition, removal, iteration, and random access of elements.
ArrayList
An ArrayList uses a dynamic array to store its elements, which provides efficient random access and iterative access to its elements. However, adding or removing elements (especially in the middle or at the beginning of the list) can be relatively slow because it may require shifting elements to maintain the list order.
Preferred Use Cases for ArrayList:
- Frequent Read Operations: If your application frequently accesses elements using the index,
ArrayListoffers better performance because it provides constant-time positional access. - Less Frequent Additions and Deletions: Especially suited for scenarios where additions and deletions are primarily at the end of the list or infrequent, as these operations can be costly due to the potential need to resize the array or shift elements.
- Memory Efficiency: Generally,
ArrayListis more memory-efficient thanLinkedList(excluding the overhead of array resizing), asLinkedListalso stores additional data for links.
LinkedList
A LinkedList in Java is a doubly-linked list implementation, meaning each element (node) contains references to both the previous and next elements in the list. This structure allows for efficient insertions and deletions anywhere in the list, as it merely requires changing the links of the neighboring nodes, but it provides slower random access, as it needs to traverse the list from the beginning or end to access an element.
Preferred Use Cases for LinkedList:
- Frequent Additions and Deletions: If your application frequently inserts and removes elements from any position in the list,
LinkedListoffers better performance since these operations can be done in constant time, assuming you have an iterator to the position. - Implementing Stacks or Queues: The
LinkedListclass is also ideal for implementing stacks and queues since it efficiently supports operations at both ends of the list (addFirst,addLast,removeFirst,removeLast). - Memory Overhead is Not a Concern: Each element in a
LinkedListstores two references (to the next and previous elements), which introduces additional memory overhead. If this is not a concern for your application, the benefits of fast insertions and deletions might outweigh this drawback.
28. Can Hashmap have null key?
Yes, One null key allowed. But multiple null value allowed.
29. Concurrent Hashmap working and it’s internal implementation.
ConcurrentHashMap in Java is a thread-safe implementation of the Map interface that provides high concurrency for retrieval and updates. It is part of the java.util.concurrent package and is designed to work efficiently in multi-threaded environments. The goal of ConcurrentHashMap is to offer better performance than Hashtable and Collections.synchronizedMap by allowing concurrent read and write operations without the need for locking the entire map.
Internal Implementation
Node Array and Chaining
- Node Array: At its core,
ConcurrentHashMapuses an array of nodes, similar to the buckets in a traditional hash table. Each node represents a key-value pair. - Chaining: In case of hash collisions (when multiple keys have the same array index),
ConcurrentHashMapuses a linked list (chain) of nodes at that index. When the number of nodes in a chain exceeds a certain threshold, the chain is converted into a balanced tree (red-black tree) to maintain efficient operations.
Concurrency Control
- Synchronized Node Access: Instead of locking entire segments of the map (as in versions before Java 8), modifications to nodes (like adding or removing key-value pairs) are controlled using synchronized blocks on the nodes themselves. This approach allows multiple threads to update different parts of the map concurrently, significantly increasing the map’s concurrency level.
- Volatile Node References: The references to the node array and the nodes themselves are marked as
volatile. This ensures that any thread accessing the map sees the most up-to-date version of these nodes and the array, which is critical for the visibility of changes across threads.
30. How to Iterate Map?
Iterating over a Map in Java can be done in several ways, depending on what you need to do with the keys, values, or both. Below are common methods for iterating through a Map, such as HashMap or TreeMap:
1. Using entrySet() to Iterate Over Key-Value Pairs
The entrySet() method returns a Set of map entries, where each entry contains a key-value pair. You can use an enhanced for-loop or iterator to loop through these entries.
Map<String, Integer> map = Map.of("Apple", 3, "Banana", 5, "Cherry", 2);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}2. Using keySet() to Iterate Over Keys
The keySet() method returns a Set of keys, and you can iterate through this set if you’re only interested in keys. If you need values as well, you’ll have to fetch them using the get() method.
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}3. Using values() to Iterate Over Values
If you only need to work with values and don’t care about the keys, you can iterate over the collection returned by the values() method.
for (Integer value : map.values()) {
System.out.println(value);
}4. Using forEach() Method (Java 8)
Java 8 introduced the forEach() method, which provides a more concise and readable way to iterate over a map using lambda expressions.
map.forEach((key, value) -> System.out.println(key + " = " + value));31. Can hashmap has null value?
Yes, Mulitple null value allowed. But only one null key allowed.
32. Hashmap, synchronizedhashmap, concurrent Hashmap.
| Characteristic | HashMap | SynchronizedHashMap | ConcurrentHashMap |
|---|---|---|---|
| Thread Safety | Not thread-safe (not safe for concurrent use). | Thread-safe (synchronized operations). | Thread-safe (supports concurrent access). |
| Concurrent Writes | Multiple threads can lead to unpredictable results or data corruption if not synchronized externally. | Supports concurrent writes with synchronized methods, but may lead to performance bottlenecks. | Supports high-concurrency writes without external synchronization. |
| Null Keys | Allows one null key and multiple null values. | Allows null keys and values. | Allows null keys and values. |
| Iterating | Safe for iterating when no structural modification is performed during iteration. | Safe for iterating when no structural modification is performed during iteration. | Safe for iterating when no structural modification is performed during iteration. |
| Performance | Generally faster for single-threaded access. | May be slower due to synchronization overhead. | Designed for high concurrency and offers better performance in concurrent scenarios. |
| Use Cases | Suitable for single-threaded scenarios or when external synchronization is provided. | Suitable for scenarios requiring synchronization with moderate concurrency. | Ideal for high-concurrency scenarios where performance is critical. |
| Example | HashMap<String, Integer> map = new HashMap<>(); | Map<String, String> sMap = Collections.synchronizedMap(new HashMap<>()); | java ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>(); |
33. Hashtable vs Hashmap.
| Characteristic | Hashtable | HashMap |
|---|---|---|
| Thread Safety | Thread-safe (synchronized operations). | Not thread-safe by default. |
| Concurrent Writes | Supports concurrent writes with synchronized methods, but may lead to performance bottlenecks. | Allows concurrent writes without synchronization, but may cause issues in concurrent scenarios. |
| Null Keys | Does not allow null keys or values. | Allows one null key and multiple null values. |
| Iterating | Safe for iterating when no structural modification is performed during iteration. | Safe for iterating when no structural modification is performed during iteration. |
| Performance | May be slower due to synchronization overhead. | Generally faster for single-threaded access. |
| Use Cases | Suitable for single-threaded or legacy applications requiring thread safety. | Suitable for single-threaded applications where thread safety is not a concern. |
| Example | Hashtable<String, Integer> table = new Hashtable<>(); | HashMap<String, Integer> map = new HashMap<>(); |
34. null key in hashset and treeset.
HashSet allows a single null value as a key. In other words, you can add null to a HashSet exactly once.
TreeSet does not allow null values as keys. If you attempt to add null to a TreeSet, it will throw a NullPointerException.
Some Miscellaneous
1. Backward Compatibility of Java 1.8.
Backward compatibility is a key feature in Java, ensuring that newer versions of the Java platform remain compatible with earlier versions. This means that code written for an older version of Java should run unchanged on a newer version of the Java runtime environment (JRE).
Key Aspects of Java 8’s Backward Compatibility:
- Binary Compatibility: Java 8 is designed to ensure that compiled Java programs (bytecode) that ran on older versions (like Java 7, Java 6) can run on the Java 8 runtime without modification. This is crucial for not breaking existing applications when upgrading the JRE.
- Source Compatibility: Java 8 aims to ensure that source code written in older Java versions can be compiled on the Java 8 compiler. However, there might be exceptions where deprecated methods are removed or certain behaviors are changed, requiring minor modifications to the source code.
- Behavioral Compatibility: While Java 8 maintains compatibility at the binary and source levels, there might be changes in the JVM’s behavior to improve performance, security, or bug fixes. These changes are generally subtle and unlikely to affect well-written applications but can impact applications relying on unspecified behaviors or using APIs in unintended ways.
2. List of Object Class method.
The Object class is a fundamental class in Java, and it serves as the superclass for all other classes in Java. It provides a set of methods that are available to all objects in Java. Here’s a list of some of the important methods provided by the Object class:
equals(Object obj): Determines if the current object is equal to the specified object. By default, it compares object references, but it can be overridden in subclasses to define custom equality.hashCode(): Returns a hash code value for the object. This method is used when objects are stored in hash-based collections likeHashMap.toString(): Returns a string representation of the object. It is often overridden in subclasses to provide meaningful string representations.getClass(): Returns the runtime class of the object. It returns aClassobject that represents the class of the object.clone(): Creates and returns a shallow copy of the object. The class must implement theCloneableinterface, and theclone()method can be overridden to provide a deep copy if necessary.finalize(): This method is called by the garbage collector before reclaiming an object’s memory. It can be overridden in subclasses to perform cleanup operations.notify(): Wakes up one of the threads that is waiting on the object’s monitor. It is used for inter-thread communication and synchronization.notifyAll(): Wakes up all threads that are waiting on the object’s monitor. It is used for inter-thread communication and synchronization.wait(): Causes the current thread to wait until another thread invokesnotify()ornotifyAll()on the object. It is used for inter-thread communication and synchronization.wait(long timeout): Similar towait(), but with a timeout. It causes the current thread to wait for a specified amount of time or until another thread invokesnotify()ornotifyAll().wait(long timeout, int nanos): Similar towait(long timeout), but with nanosecond precision for the timeout.finalize(): Deprecated method in Java 9 and later. It was used for finalization of objects before they were garbage collected, but it’s not recommended for use in modern Java.
These methods provide fundamental functionality that all Java objects inherit. Subclasses can override these methods to customize their behavior as needed.
3. list of modifiers in java
In Java, modifiers are keywords that you add to those definitions to change their meanings. Java has two categories of modifiers:
1. Access Modifiers: Control the visibility of classes, methods, and variables.
private: The access level of a private modifier is only within the class. It cannot be accessed from outside the class.default(no modifier): If no access modifier is specified, it’s treated as default. This means the class or member is accessible only within its own package.protected: The protected access level provides access within the same package or subclasses in different packages.public: The class, method, or variable is accessible from any other class.
2. Non-access Modifiers: Provide other functionalities.
static: Belongs to the class rather than an instance of the class. Can be accessed without creating an instance of a class.final: For classes, it means the class cannot be subclassed. For methods, it means the method cannot be overridden by subclasses. For variables, it means that once they are initialized, their value cannot be changed.abstract: Cannot be used to instantiate objects and can only be used as a superclass for other classes. Methods defined within an abstract class can only be implemented by its subclass unless the abstract class itself is a subclass of another abstract class.synchronized: On methods or statements, it means it can only be accessed by one thread at a time.volatile: Indicates that a variable’s value will be modified by different threads.transient: Prevents fields from being serialized.native: Specifies that a method is implemented in native code using JNI (Java Native Interface).
Modifiers can be combined to adjust the meaning of definitions in various ways. However, not all modifiers are applicable to all kinds of elements. For example, synchronized, transient, and volatile are not applicable to classes or interfaces.
4. What are containers?
Containers are a lightweight, stand-alone, executable software package that includes everything needed to run a piece of software, including the code, runtime, libraries, environment variables, and config files. Containers are designed to run across different computing environments in a consistent and efficient manner. They encapsulate an application’s dependencies, making it easy to develop, deploy, and scale applications across various environments without the need for additional configuration.
Container Technology:
- Docker: One of the most popular container platforms, Docker simplifies the process of creating, deploying, and running containers.
- Kubernetes: An open-source platform designed to automate deploying, scaling, and operating application containers, often used to manage Docker containers.
- Containerd: An industry-standard container runtime focused on simplicity, robustness, and portability.
5. Which is better way to declare a string?
In Java, strings can be declared in two main ways: using the String class and using string literals.
Using the String class
String str = new String("Hello, World!");Advantages:
- Provides more control over the creation of the string, allowing you to specify the exact content and behavior.
- Useful when you need to create strings dynamically or when you want to work with string manipulation methods extensively.
Disadvantages:
- It requires the use of the
newkeyword and explicit instantiation, making the code longer. - It may create unnecessary string objects in the string pool if not used carefully.
Using string literals:
String str = "Hello, World!";Advantages
- It’s a straightforward way to declare strings, making the code cleaner and easier to understand.
- String literals are automatically interned (pooled) by Java, so multiple references to the same string literal will point to the same object, saving memory.
Disadvantages
- You cannot modify the content of a string literal once it’s created. String literals are immutable.
Which to Choose:
- If you need to create a fixed string that won’t change during the program’s execution, using string literals is the preferred and more efficient way. It also makes your code more readable.
- If you need to create strings dynamically or modify them extensively, you can use the
Stringclass constructor. However, consider using methods likeStringBuilderfor efficient string manipulation in such cases.
6. What is scalability?
Scalability is the ability of a system, network, or process to handle a growing amount of work, or its potential to accommodate growth. It is a crucial characteristic for systems, applications, and infrastructures to ensure they can adapt to increased demands without compromising performance, functionality, or user experience. Scalability can involve hardware, software, or a combination of both, and it is often a primary consideration in the design and deployment of applications and systems.
Types of Scalability:
- Vertical Scalability (Scaling Up): This involves adding more resources to the existing nodes in a system, such as adding more RAM, CPUs, or storage to a server. It’s often simpler to implement but can hit physical or practical limits.
- Horizontal Scalability (Scaling Out/In): This involves adding more nodes to a system, such as adding more servers to handle increased load. This type is more complex but can virtually offer unlimited scaling capabilities.
- Diagonal Scalability: A hybrid approach that involves both scaling up and scaling out as necessary. It allows for flexible scaling strategies according to specific needs and conditions.
7. Explain SOLID principles.
SOLID is an acronym that represents a set of five design principles for writing maintainable and scalable software.
- Single Responsibility Principle (SRP): This principle states that a class should have only one reason to change. In other words, a class should have only one responsibility.
- Open/Closed Principle (OCP): The Open/Closed Principle suggests that software entities (classes, modules, functions) should be open for extension but closed for modification.
- Liskov Substitution Principle (LSP): The Liskov Substitution Principle states that objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program.
- Interface Segregation Principle (ISP): The Interface Segregation Principle suggests that clients should not be forced to depend on interfaces they do not use.
- Dependency Inversion Principle (DIP): The Dependency Inversion Principle emphasizes two key points:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
8. Junit with Code example.
Calculator.java
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}CalculatorTest.java
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
// Create an instance of Calculator to test
Calculator calculator = new Calculator();
@Test
public void testAdd() {
int result = calculator.add(2, 3);
assertEquals(5, result); // Verify that the result is as expected
}
@Test
public void testSubtract() {
int result = calculator.subtract(5, 2);
assertEquals(3, result); // Verify that the result is as expected
}
}9. How to do a Boolean algebra evaluation in Java?
In Java, you can evaluate Boolean algebra expressions using logical operators. Boolean algebra deals with true and false values and includes operations such as AND, OR, NOT, etc. In Java, these are represented by && (AND), || (OR), ! (NOT), among others. Here’s a basic guide on how to perform Boolean algebra evaluations:
Logical Operators in Java:
- AND (
&&): Evaluates totrueif both operands are true; otherwise, it’s false. - OR (
||): Evaluates totrueif at least one of the operands is true; otherwise, it’s false. - NOT (
!): Inverts the value of a boolean, turningtruetofalseand vice versa. - XOR (
^): Evaluates totrueif the operands are different; otherwise, it’s false.
10. Nosql db vs rdbms.
NoSQL databases and Relational Database Management Systems (RDBMS) represent two different approaches to data storage and manipulation.
NoSQL Database
- Typically schema-less, NoSQL databases allow for the storage of unstructured, semi-structured, or structured data. They support a variety of data models, including key-value, document, wide-column, and graph models.
- Designed for horizontal scalability, NoSQL databases can easily distribute data across multiple servers or nodes, making them well-suited for large-scale data operations and cloud environments.
- Ideal for big data applications, real-time web applications, content management systems, and any scenario where the data model is not fully known in advance or is expected to change over time.
RDBMS
- RDBMSes use a structured schema, defined by tables with fixed rows and columns. The schema must be defined before data can be stored, and all data must conform to the specified structure.
- Traditional RDBMSes are designed for vertical scalability, meaning that to handle increased loads, a more powerful server is typically required. Horizontal scaling is possible but often complex and expensive.
- Well-suited for applications requiring complex transactions, detailed reports, or where the data structure is well-defined and unlikely to change frequently, such as financial software, customer relationship management (CRM) systems, and other enterprise applications.
11. Domain driven and Test driven design.
Domain-Driven Design (DDD) and Test-Driven Development (TDD) are both methodologies used in the software development process, but they focus on different aspects of that process and serve different purposes.
Domain-Driven Design (DDD)
Domain-Driven Design is an approach to software development that centers on the complexity of the domain (or the business) that the software is addressing. The core premise of DDD is to place the primary focus on the domain logic, thoroughly understanding the domain itself, and then designing a software model that accurately reflects and addresses that domain.
Purpose: DDD aims to create software models that are deeply aligned with underlying business concepts, facilitating better communication among team members and stakeholders, and leading to more relevant and adaptable software systems.
Test-Driven Development (TDD)
Test-Driven Development is an agile development technique where developers write tests for a new feature before they write the code to implement that feature. The process follows a short, repeatable cycle: Red-Green-Refactor.
Purpose: The goal of TDD is to ensure that software is thoroughly tested and leads to cleaner, more bug-free code. It encourages developers to think through their design and requirements before writing the code, resulting in a more deliberate and considered implementation.
Comparing DDD and TDD
- Focus: DDD focuses on the domain and modeling complex business scenarios, while TDD focuses on ensuring code correctness through a test-first approach.
- Outcome: DDD aims to create a model that accurately reflects the business domain, improving communication and software adaptability. TDD aims to create a robust, bug-free codebase.
- Process: DDD involves extensive upfront domain analysis and continuous refinement of the domain model. TDD follows a cyclic process of writing tests, writing code to pass the tests, and refactoring.
12. Flatfile vs blob for image(picture) storage.
Storing images or pictures in a database can be approached in several ways, two common methods being flat files and Binary Large Objects (BLOBs).
Flat File Storage
In the flat file approach, images are stored on the filesystem of the server, and the database stores the file paths or URLs to these images. File systems are generally more efficient and cost-effective for storing large files like images.
BLOB Storage
BLOB storage involves storing the image data directly in the database as a BLOB field. Having images stored directly in the database can simplify architecture by reducing the need for separate storage management.
13. intern method of string scp.
The intern() method in Java is used to add a String to the pool of interned strings and return a reference to the interned string. The string pool (or string intern pool) is a special area in the Java heap memory where strings are stored, and each unique string value is stored only once. This means that if two string objects have the same value, they will reference the same memory location in the string pool.
Here’s how the intern() method works:
- If a string with the same contents as the calling string already exists in the pool, a reference to that interned string is returned.
- If the string does not exist in the pool, a new string with the same contents as the calling string is added to the pool, and a reference to that interned string is returned.
Example
String s1 = new String("hello"); // Create a new string object
String s2 = "hello"; // Create a string literal (interned)
String s3 = s1.intern(); // Intern s1 and get a reference to the interned string
System.out.println(s1 == s2); // false, as they are different objects
System.out.println(s2 == s3); // true, as they both reference the interned stringIn this example, s1 is created as a new String object, while s2 is created as a string literal, which is automatically interned. When we call s1.intern(), it adds the contents of s1 to the string pool and returns a reference to the interned string, which is the same as s2. Therefore, s2 == s3 is true.
The intern() method can be useful in scenarios where you want to ensure that strings with the same contents share the same memory location, which can help reduce memory usage and optimize string comparison operations. However, it should be used judiciously, as excessive use of intern() can also lead to increased memory usage and potentially impact performance.
14. What is Reactive Programming?
Reactive programming is a programming paradigm oriented around data streams and the propagation of change. It emphasizes the development of systems that are responsive, resilient, elastic, and message-driven. In reactive programming, the focus is on building asynchronous, non-blocking, and event-driven applications that can easily respond to streams of inputs or events over time.
Implementation:
Reactive programming can be implemented in various programming languages through libraries and frameworks designed for this purpose. Examples include:
- RxJava and Project Reactor for Java: These libraries provide APIs for composing asynchronous and event-based programs using observable sequences.
- RxJS for JavaScript: A library for composing asynchronous and event-based programs by using observable sequences in the JavaScript language.
Use Cases:
Reactive programming is particularly suited for applications that require high responsiveness and scalability, such as:
- Real-time data processing systems
- Highly interactive web applications
- Applications dealing with streams of data, like IoT devices
- Back-end systems that scale dynamically based on demand
15. Difference between Web Server and Application Server.
Web Server:
The primary function of a web server is to handle HTTP requests and responses. It is responsible for serving static web content like HTML pages, images, CSS files, and JavaScript files to clients (usually web browsers).
Popular application servers include Apache Tomcat, WildFly (formerly JBoss), IBM WebSphere, and Microsoft Azure App Service.
Popular web servers include Apache HTTP Server, Nginx, Microsoft Internet Information Services (IIS), and LiteSpeed.
Application Server:
The primary function of an application server is to execute and manage the business logic and application code of a web application. It handles dynamic content generation, database interactions, and application-specific functionality.
Application servers are designed to execute server-side code (e.g., Java, .NET, Python) and generate dynamic content in response to client requests. They can communicate with databases and other services to process data.
Popular application servers include Apache Tomcat, WildFly (formerly JBoss), IBM WebSphere, and Microsoft Azure App Service.
16. Difference Between webclient and restclient.
The Difference between WebClient and RestClient often comes up in the context of making HTTP requests in various programming environments.
In the Context of the Spring Framework (Java):
WebClient:
- Introduced in Spring WebFlux as part of the Spring 5 release.
- Designed to work in reactive applications, supporting both synchronous and asynchronous operations.
- It’s intended to eventually replace
RestTemplateas the main method of making HTTP requests. - Works well for both traditional RESTful services and more modern, event-driven systems where streams of data are consumed or produced.
- Provides a more modern, functional, and powerful way to perform HTTP operations, leveraging the reactive programming model.
RestTemplate:
- Introduced earlier in the Spring Framework and has been the mainstay for synchronous HTTP requests and RESTful services.
- Does not support non-blocking I/O operations, making it less suitable for highly scalable, event-driven applications.
- As of Spring 5, it’s in maintenance mode, with the recommendation to migrate to
WebClientfor future development. - Easier to use for simple use cases and small-scale applications, especially those that don’t require the scalability and flexibility provided by reactive programming.
17. List of Java Design patterns.
Java Design pattern is categories between three categories.
Creational Design Patterns: Mainly focus on how object will be created. Design pattern comes in this are:
1) . Singleton Design Pattern
2). Factory Method Pattern
3). Abstract Factory Pattern
4). Builder Pattern
5). Prototype Pattern
Structural Design Patterns: This is mainly focus on Class creations. Structural design patterns provide different ways to create a Class structure. Design pattern comes under structural design pattern are:
1). Adapter Pattern
2). Bridge Pattern
3). Composite Pattern
4). Decorator Pattern
5). Facade Pattern
6). Flyweight Pattern
7). Proxy Pattern
Behavioral Design Patterns: Behavioral design patterns are a category of design patterns that deal with object collaboration, responsibilities, and the communication between objects. These patterns focus on how objects interact and communicate with one another to achieve a specific behavior.
1). Observer Pattern
2). Strategy Pattern
3). Command Pattern
4). Chain of Responsibility Pattern
5). State Pattern
6). Visitor Pattern
7). Interpreter Pattern
8). Iterator Pattern
9). Template Method Pattern
10). Mediator Pattern
Basic Java Coding Problems
Visit our “Basic Java Coding Problems” section to sharpen your coding skills for Java interviews. This part of our website has a some simple java coding questions that challenge you to solve problems using Java. From simple coding tasks like reverse string to more complex problems that test your logic and understanding, this section has it all. It’s perfect for anyone who is just starting to learn Java or those who want to brush up on their coding skills for their next java developer interview. By practicing these questions, you can build confidence and become well-prepared to tackle coding problems with ease in your interviews.
1. For Given String that contains number as well. Find the sum of all numbers.
public class Main {
public static void main(String[] args) {
String str = "prakash123kum34";
char[] ch = str.toCharArray();
int sum=0;
for(Character ch1 : ch){
if(Character.isDigit(ch1)){
sum=sum+Integer.parseInt(String.valueOf(ch1));
}
}
System.out.print("Sum= "+sum);
}
} Output
Sum= 13
Using Java 8
String str = "prakash123kum34";
int intSum = str.chars().filter(s->Character.isDigit(s)).map(s->Character.getNumericValue(s)).reduce(0, (a, b) -> a+b);
System.out.println("Integer sum in given string "+intSum);2. Reverse String in Java.
Program 1:
Here we are using third variable and applying swapping logic.
public class Main {
public static void main(String[] args) {
String str = "praka789shkum88";
char[] ch = str.toCharArray();
int i=0;
int j=ch.length-1;
while(i<ch.length/2){
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
i++;
j--;
}
System.out.print(new String(ch));
}
} Output
88mukhs987akarp
Program 2:
Here we are not using third variable and applying swapping logic.
public class Main {
public static void main(String[] args) {
String str = "praka789shkum88";
char[] ch = str.toCharArray();
int i=0;
int j=ch.length-1;
while(i<ch.length/2){
// XOR swap to swap characters without a third variable
ch[i] = (char) (ch[i] ^ ch[j]);
ch[j] = (char) (ch[i] ^ ch[j]);
ch[i] = (char) (ch[i] ^ ch[j]);
i++;
j--;
}
System.out.print(new String(ch));
}
}Output
88mukhs987akarp
Program 3: Using Java 8
String revereString = str.chars().mapToObj(s->String.valueOf((char) s)).reduce("",(a,b)->b+a);
System.out.println("Reversed String java 8 : "+revereString);3. Reverse the words in String in Java
Program 1
public class Main {
public static void main(String[] args) {
String str = "the sky is blue";
String[] words = str.split(" ");
int i = 0;
int j = words.length-1;
while(i<j){
String temp = words[i];
words[i] = words[j];
words[j] = temp;
i++;
j--;
}
StringBuilder strB = new StringBuilder();
for (String str1 : words) {
strB.append(str1);
strB.append(" ");
}
System.out.print(strB);
}
}Output
blue is sky the
4. Remove the repeated Character in String.
Program 1
import java.util.*;
public class Main {
public static void main(String[] args) {
String str = "the sky is blue";
char ch[] = str.toCharArray();
Map<Character,Integer> map = new HashMap<>();
for(Character ch1 : ch){
if(map.containsKey(ch1)){
map.put(ch1,map.get(ch1)+1);
}else{
map.put(ch1,1);
}
}
StringBuilder strB = new StringBuilder();
for(Map.Entry<Character,Integer> entry: map.entrySet()){
if(!(entry.getValue()>1)){
strB.append(entry.getKey());
}
}
System.out.println(strB);
}
} Output
btuhyikl
Program 2: Using Java 8
String distinct = str.chars().mapToObj(s->String.valueOf((char) s)).distinct().collect(Collectors.joining());
System.out.println("Reversed String java 8 : "+distinct);Output
Remove Duplicates java 8 : 123 HeloWrd
5. Find Average of ArrayList Integers.
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5);
double average = list.stream().mapToDouble(Integer::intValue).average().getAsDouble();
System.out.println(average);
}
}Output
3.0
Program using Java 8
Integer[] arr = {1,2,3,4,5};
double avg = Arrays.stream(arr).mapToDouble(Integer::doubleValue).average().getAsDouble();
System.out.println("average value : "+avg);average value : 3.0
6. Substring Counter problem
import java.util.*;
public class Main {
public static void main(String[] args) {
String input = "yyuolhellooohellhello";
String substring = "hello";
int count = 0;
int index = 0;
while(index != -1){
index = input.indexOf(substring,index);
if(index != -1){
count++;
index = index + substring.length();
}
}
System.out.println(substring +" occurred = " + count + " times");
}
}Output
hello occurred = 2 times
Scenario Based Java Coding Problems
Dive into our “Scenario Based Java Coding Problems” section to practice for Java interviews that involve real-world situations. This part of our website features a collection of coding questions that ask you to solve practical problems using Java. You’ll face scenarios like sorting data, searching for items, and applying some logics on objects. These questions help everyone, from beginners to those with some Java experience, learn how to apply Java in everyday tasks. Practicing these scenario-based problems will boost your confidence and ensure you’re ready to handle similar challenges smoothly in your interviews.
1. Using Lambda Function print given List of Integers
import java.util.*;
public class Main
{
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.forEach(x->System.out.println(x));
}
}2. Input- [2,1,3,2,4,5,3,5,6] Output- [1,2,3,4,5,6] How to do this?
import java.util.*;
public class Main
{
public static void main(String[] args) {
List<Integer> list = Arrays.asList(2,1,3,2,4,5,3,5,6);
list.stream().sorted().distinct().forEach(System.out::println);
}
}3. Use stream to print employee whose salary>10000.
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
employees.stream().filter(s->s.getSalary()>10000).forEach(s->System.out.println(s));
}
}Output
Employee{name='sonam', salary=20000.21}
Employee{name='rema', salary=30000.21}
4. Use stream to print only name of employee with salary>10000.
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
employees.stream().filter(s->s.getSalary()>10000).forEach(s->System.out.println(s));
}
}Output
sonam rema
5. How to get occurrence of elements of List?
Occurrence in Integer list
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FindOccurrence {
public static void main(String ...a){
int[] list = {1,2,3,4,1,2,5,6,2,3,1};
Map<Integer,Integer> map= new HashMap<>();
for(int i:list){
if(map.containsKey(i)){
map.put(i,map.get(i)+1);
}else{
map.put(i,1);
}
}
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
System.out.println(entry.getKey() +" "+entry.getValue());
}
}Output
1 3 2 3 3 2 4 1 5 1 6 1
Occurrence in String
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FindOccurrence {
public static void main(String ...a){
int[] list = {1,2,3,4,1,2,5,6,2,3,1};
String str = "interviewexepert";
Map<Character,Integer> map= new HashMap<>();
for(char ch:str.toCharArray()){
if(map.containsKey(ch)){
map.put(ch,map.get(ch)+1);
}else{
map.put(ch,1);
}
}
for(Map.Entry<Character,Integer> entry : map.entrySet()){
System.out.println(entry.getKey() +" "+entry.getValue());
}
}Output
p 1 r 2 t 2 e 5 v 1 w 1 x 1 i 2 n 1
6. How many elements will be stored in HashMap -> map.put(null,a),map.put(“”,b),map.put(“null”,c)
import java.util.HashMap;
import java.util.Map;
public class MapScenario1 {
public static void main(String ...a){
Map<String,String> hMap = new HashMap<String,String>();
hMap.put("null","Prakash");
hMap.put("","Kumar");
hMap.put(null,"Badal");
hMap.forEach((k,v)->System.out.println("Key= " +k+" Value= "+v));
}
}Output
Key= Value= Kumar Key= null Value= Badal Key= null Value= Prakash
7. First occurrence of duplicate number from a list.
import java.util.HashMap;
import java.util.Map;
public class FirstOccurrence {
public static void main(String ...a){
int[] list = {1,2,3,4,1,2,5,6,2,3,1};
Map<Integer,Integer> map = new HashMap<>();
for(int i:list){
if(map.containsKey(i)){
System.out.println("first occurrence =" + i);
break;
}else{
map.put(i,1);
}
}
}
}Output
first occurrence =1
8. Print name > 15000, print name startswith “s”.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
employees.stream().filter(s->s.getSalary()>15000 && s.getName().startsWith("s")).forEach(System.out::println);
}
}Output
Employee{name='sonam', salary=20000.21}
9. Create Employee Object which is having empId, empName, empSal and then sort based on empName.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
employees.stream().sorted(Comparator.comparing(s->s.getName())).forEach(s->System.out.println(s.getName()));
}
}10. List convert into Map<empName,empSalary> using java8.
import java.util.*;
import java.util.stream.Collectors;
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
Map<String ,Double> map = employees.stream().collect(Collectors.toMap(Employee::getName, Employee::getSalary));
map.forEach((k,v)->System.out.println(k+" "+v));
}
}Output
prakash 5000.21 rema 30000.21 sonam 20000.21
11. Sort map based on map value using java8.
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class MapSortingJava8 {
public static void main(String ...a){
Map<String,Integer> hMap = new HashMap<>();
hMap.put("prakash",11);
hMap.put("akash",2);
hMap.put("rakesh",7);
hMap.put("sohan",5);
Map<String,Integer> sortedMap = hMap.entrySet().stream().sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(e1,e2)->e1,
LinkedHashMap::new
));
sortedMap.forEach((k,v)->System.out.println(k+" "+v));
}
}Output
akash 2 sohan 5 rakesh 7 prakash 11
12. Count duplicate characters in a given String.
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FindOccurrence {
public static void main(String ...a){
String str = "interviewexpert";
Map<Character,Integer> map= new HashMap<>();
for(char ch: str.toCharArray()){
if(map.containsKey(ch)){
map.put(ch,map.get(ch)+1);
}else{
map.put(ch,1);
}
}
for(Map.Entry<Character,Integer> entry : map.entrySet()){
if(entry.getValue()>1){
System.out.println(entry.getKey());
}
}
}Output
r t e i
13. Second highest salary query.
Select salary from employee orderBy salary DESC limit 1 offset 1;14. Remove duplicates of Employee objects from List
import java.util.*;
import java.util.stream.Collectors;
class Employee1 {
private int id;
private String name;
private int roll;
public Employee1(int id, String name, int roll) {
this.id = id;
this.name = name;
this.roll = roll;
}
// Getters, setters, and other methods
public int getId(){
return this.id;
}
public String getName(){
return this.name;
}
@Override
public boolean equals(Object o) {
if(o == null || getClass() != o.getClass()){
return false;
}
if(this==o){
return true;
}
Employee1 e = (Employee1) o;
return (this.id == e.getId());
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
public class RemoveDuplicateEmployee {
public static void main(String[] args) {
List<Employee1> employeeList = new ArrayList<>();
employeeList.add(new Employee1(1, "Alice",3));
employeeList.add(new Employee1(2, "Bob", 5));
employeeList.add(new Employee1(1, "Alice", 2)); // Duplicate
employeeList.add(new Employee1(3, "Charlie", 1));
employeeList.add(new Employee1(2, "Bob", 6)); // Duplicate
// Remove duplicates using Java 8 Stream API and distinct()
List<Employee1> uniqueEmployees = employeeList.stream()
.distinct()
.collect(Collectors.toList());
// Print the list without duplicates
uniqueEmployees.forEach(employee -> System.out.println("ID: " + employee.getId() + ", Name: " + employee.getName()));
}
}Output
ID: 1, Name: Alice ID: 2, Name: Bob ID: 3, Name: Charlie
15. Find All Pairs in array to target sum in Java.
import java.util.HashMap;
public class FindPairsWithSum {
public static void main(String[] args) {
int[] arr = {5, 2, 7, 1, 9, 3};
int targetSum = 10;
findPairsWithSum(arr, targetSum);
}
public static void findPairsWithSum(int[] arr, int targetSum) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
int temp = targetSum - arr[i];
if (map.containsKey(temp)) {
System.out.println("Pair are: (" + arr[i] + ", " + temp + ")");
}
map.put(arr[i], i);
}
}
}Output
Pair are: (9, 1) Pair are: (3, 7)
16. Reverse the sorted Value of Employee on basis of salary
import java.util.*;
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
public class SortEmployee {
public static void main(String ...a){
List<Employee> employees =
Arrays.asList(new Employee("sonam",20000.21),
new Employee("prakash",5000.21),
new Employee("rema",30000.21));
employees.stream().sorted(Comparator.comparing(Employee::getSalary).reversed()).forEach(s->System.out.println(s.getName()));
}
}Output
rema sonam prakash
17. Given Employee details, count the employee in each department and also print name whose salary is greater that 10,000.
import java.util.*;
import java.util.stream.*;
class Employee{
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Employee(String name, String department, double salary, int age) {
this.name = name;
this.department = department;
this.salary = salary;
this.age = age;
}
private String name;
private String department;
private double salary;
private int age;
}
public class DemoClass {
public static void main(String ...a){
List<Employee> list = Arrays.asList(new Employee("prakash1","dep1", 1200.3, 21),
new Employee("prakash2","dep1", 12000.3, 21),
new Employee("prakash3","dep2", 1200.3, 21),
new Employee("prakash4","dep2", 10000.3, 21),
new Employee("prakash5","dep2", 12000.3, 21));
Map<String,List<Employee>> map = list.stream().collect(Collectors.groupingBy(Employee::getDepartment));
map.forEach((department,empList)->{
long c = empList.stream().count();
System.out.println(department +" count = "+ c);
empList.stream().filter(s->s.getSalary()>10000).forEach(s->System.out.println(s.getName()));
});
}
}
Output
dep1 count = 2 prakash2 dep2 count = 3 prakash4 prakash5