Looking for Core Java interview questions and answers? Look no further! This guide has everything you need to ace your interview.
Are you preparing for a Java interview and feeling overwhelmed by the vast amount of knowledge you need to have at your fingertips? Don't worry, we've got you covered! In this article, we have compiled a comprehensive list of 90 core Java interview questions and answers that you must know to ace your interview.
Java Basics are the foundation of learning and mastering the Java programming language. It is important to understand these basics in order to move on to more complex topics such as Object-Oriented Programming, Database Connectivity, and GUI development.
The first step in understanding Java Basics is understanding the fundamentals of the language itself. This includes a basic overview of Java syntax, classes, packages, interfaces, objects, variables, and methods. It is important to understand topics such as I/O Streams and Serialization.
Once you have familiarized yourself with basic language features and syntax you can begin exploring other aspects of the language such as Object-Oriented Programming (OOP) concepts like inheritance and polymorphism.
Let's get started with the first section of Core Java interview questions and answers!
1. What are the differences between final, finally, and finalize in Java?
final: The final keyword is an important tool in Java that allows developers to create classes, methods, and variables that cannot be modified. A final class cannot be extended, a final method cannot be overridden, and a final variable can only be initialized once. This is useful for ensuring that the behavior of a certain class or method remains consistent throughout the application.
Using the final keyword can provide a performance boost to an application because it allows the compiler to optimize the code more efficiently. Additionally, it makes code easier to debug and maintain since the behavior of a certain class or method cannot be modified by other code.
finally: The finally block in Java is a part of the try/catch exception handling system. It is used to ensure that a code block gets executed regardless of whether an exception has been thrown or not. This makes it ideal for resource cleanup tasks such as closing database connections, releasing resources, and cleaning up any other state information.
In the event of an exception being thrown in the try block, the finally block will always be executed, regardless of whether or not the exception was caught. Typically, the finally block is utilized for the purpose of cleaning up resources that were allocated in the try block, such closing open files or releasing database connections. It is also possible to use a finally block without a catch clause if no special handling of exceptions is required. In this case, the finally block will execute regardless of whether an exception has been thrown or not.
finalize: Finalize is a method in Java that can be overridden to perform cleanup activities when an object is about to get garbage collected. It runs just before the object is garbage collected and allows for the release of any resources that may need to be released prior to being destroyed. Finalize is usually used when an object requires some sort of resource or connection, like file handles, network connections, or database connections.
The finalize method is declared as protected and should not be called directly, as it is automatically called by the garbage collector when it determines that an object is no longer reachable. The finalize method takes no arguments and has a void return type. It can throw any exception, but in practice, throwing exceptions from finalize rarely works since the exception may not be caught by the application code. For this reason, it is recommended to not throw any exceptions from finalize.
2. How do you differentiate between primitive and reference data types in Java?
Primitive Types: Primitive types are the basic data types of a programming language. These are also known as built-in or native data types and are the most commonly used data types in any programming language. In the Java programming language, there are 8 primitive types: byte, short, int, long, char, float, double, and boolean. These types include numbers, characters, booleans, and void.
Reference Types: Reference types are data types that refer to objects. They can be used to store a reference to an object instead of the actual object itself. Reference types include classes, arrays, and interfaces. Classes define the structure of an object and provide methods for manipulating them. Arrays are collections of objects that can be accessed using indexes. Interfaces provide a contract between a class and its clients.
3. What is the role of the Java Class Loader?
The Java Class Loader is responsible for loading Java classes into the JVM during runtime. It works by reading the compiled .class files, allocating memory for them, and making them part of the JVM's runtime environment.
The Class Loader not only loads classes from the local file system but also from remote locations such as a web server or a database. It resolves class dependencies, resolves class versions, and verifies the bytecode to make sure it is valid.
4. Explain the difference between == and equals() in Java.
The equals() method is a fundamental part of the Java language. It is used to compare the content of two objects for equality. The equals() method is defined in the Object class and can be overridden for custom comparison logic.
When comparing two objects using the equals() method, the following rules are applied:
- 1If both objects are null, they are considered equal.
- 2If one object is null and the other isn’t, they are not considered equal.
- 3If both objects are of the same type, then their content is compared. The comparison is done by comparing each field of the objects to determine if they match.
- 4If both objects are different types, they are not considered equal even if their content is the same.
Used for reference comparison. Compares two objects to determine if they are pointing to the same location in memory. The == operator is used to compare two objects in Java. It is not a comparison of content, but rather a comparison of references - it checks to see if both objects are pointing to the same location in memory. If the two references are pointing to the same object in memory, then the two objects are considered equal. However, if they are pointing to different objects, even if their content is the same, then they are not considered equal.
5. How does the switch statement work with Enums in Java?
When using a switch statement with Enums in Java, the case labels should match the constants defined in the Enum. This is because the switch statement's case labels must be compile-time constants, and Enum values are just that. By matching the case labels to an Enum constant, it makes your code more readable and maintainable.
Here's an example:
6. What is type casting, and how does it differ between primitive and reference types?
Primitive Type Casting: Primitive Type Casting is a process in which one type of primitive data is converted into another type. In Java, there are two types of casting: Widening Casting (automatically) and Narrowing Casting (manually). Widening casting converts a smaller type to a larger type size while narrowing casting converts a larger type to a smaller size type. For example, widening casting can convert a float to a double, while narrowing casting can convert an int to a byte.
Reference Type Casting: Reference Type Casting is the process of converting one type of reference data into another type. In Java, there are two types of casting: Upcasting and Downcasting. Upcasting is the process of converting a subclass object into its superclass object while downcasting is the process of converting a superclass object into its subclass object. For example, upcasting can convert a String object into an Object while downcasting can convert an Object into a String.
In Java, type casting can be a useful tool for working with objects of different classes. For example, you can use it to convert an object of one class into an object of another class for use in a program. This can be particularly helpful when writing code that uses multiple classes or when dealing with objects from external sources.
Type casting can also be used to create custom types or objects with certain properties. It is important to note that type casting should only be used if absolutely necessary, as it can lead to unexpected results and errors in the code.
7. Describe the lifecycle of a Java object.
Creation: Java objects are created using the new keyword. When the new keyword is used, a constructor is called to create an instance of the object. This constructor allocates memory for the object and initializes its fields with their default values.
Initialization: Once memory has been allocated for an object, constructors can be used to initialize it with specific values. Constructors can also invoke methods on the object to perform any setup that is required.
Use: Once an object has been created and initialized, it can be used for its intended purpose. Its fields and methods can be accessed and manipulated as needed by the application.
Inaccessible: Objects become inaccessible when they go out of scope (e.g., local variables) or are explicitly set to null. When this happens, the object is no longer available to the application and its memory can be reclaimed by the garbage collector.
Garbage Collection: Java's garbage collector periodically scans through heap memory and reclaims any objects that have become inaccessible. This frees up space on the heap for new objects to be created.
8. What is the importance of the main method in Java?
The main method serves as the entry point for a Java application. It is a public and static method that can be called by the Java Virtual Machine (JVM) without having to create an instance of the class in which it is declared. The signature of the main method is public static void main(String args) where "public" indicates that the method can be accessed by any other class , "static" indicates that the method can be invoked without an instance of the class being created, and "void" indicates that the method does not return a value. The String array parameter args is used to pass command-line arguments to the application.
9. How do you differentiate between static and non-static fields and methods?
Static Fields/Methods: Static fields and methods are members of a class that are not associated with any particular object instance. They can be accessed directly through the class name without having to create an object instance first. Static fields are typically used to store information or data that is common across all instances of an object, such as constants or configuration settings. Static methods are typically used for utility functions that don’t need access to instance variables or properties.
Non-static Fields/Methods: Nonstatic fields and methods (also known as instance variables and methods, respectively) are associated with the instances of a class. They belong to each individual object of a class, rather than to the entire class itself. To access them, you must create an instance of the class by using the new keyword. Once you have created an instance, you can access nonstatic fields and methods using the dot notation.
10. What is a package in Java, and why is it used?
A package is a collection of related classes and interfaces that provide access control and namespace management. Packages allow you to organize your code into logical units, making it easier to find related classes and reuse them in different programs. Packages also help to avoid name clashes between different libraries or APIs.
When organizing your packages, use meaningful names that clearly describe the purpose of the package and its contents. This makes it easier for other developers to understand what is included in each package. You can also use packages to restrict access to certain classes by setting the appropriate access modifiers on them.
Object-Oriented Programming (OOP) Concepts
Object-oriented programming (OOP) is a popular programming methodology that allows developers to create programs and applications with reusable, self-contained sections of code known as objects. OOP is based on the concept of objects, which are data structures that contain data fields and methods.
By making use of encapsulation, abstraction, inheritance, and polymorphism, OOP simplifies software development and maintenance.
11. Can you explain the concept of inheritance and its importance in Java?
The concept of inheritance is important in Java because it allows developers to model complex relationships between classes while maintaining a high level of extensibility. It also enables developers to create class hierarchies and sub-class other classes to build on existing functionality or create new features. This makes the development process much more efficient and helps ensure that code does not need to be rewritten every time a new feature is added.
Inheritance also makes it easier to debug code and maintain programs. By creating a class hierarchy, it becomes easier to find errors in the code by tracing inheritance relationships. Additionally, since inheritance enables developers to extend existing functionality without having to rewrite code, it is much easier to make changes and add new features. This makes programs more efficient and saves time for developers. Furthermore, by taking advantage of polymorphism, developers can write code that works for a variety of related classes with minimal effort.
12. How is abstraction implemented in Java?
Interfaces and abstract classes share similarities in that they both have methods without a body. However, unlike abstract classes, interfaces cannot be instantiated to create objects. A class is able to implement an interface by providing an implementation for all of its methods. It is necessary for any class that implements an interface to provide implementations for all of the methods that are defined in the interface.
Abstraction is often used to simplify complex systems and make them easier to understand. It also helps in reducing code duplication and makes code more maintainable. By using abstraction, developers can extend the functionality of a system without affecting existing code or structure.
13. What distinguishes an interface from an abstract class?
The primary difference between an interface and an abstract class is that an interface can only contain method signatures and fields, while an abstract class can contain method signatures, fields, and implementations. In other words, interfaces only provide the skeleton of a class, but abstract classes provide some concrete implementation.
Interfaces are used when there is no implementation for a particular behavior or function. It provides a clear contract of what needs to be implemented without actually providing any code. Interfaces are intended to be used as a “template” for other classes to implement certain behaviors or functions.
Abstract classes are more flexible than interfaces since they can provide concrete implementations for some of the methods in addition to defining the signature for those methods. Abstract classes are also useful when implementing inheritance, as they can provide a common base class for other classes to extend.
14. What is polymorphism? Provide a real-world analogy to explain it.
Polymorphism can be divided into two categories: compile-time polymorphism and runtime polymorphism. Compile-time polymorphism is achieved through method overloading, while runtime polymorphism is achieved through method overriding.
Polymorphism is a powerful concept that allows developers to write code that is more flexible and extensible. It helps to make application logic simpler by enabling the same method or variable to operate on different objects at different times. By taking advantage of polymorphism, developers are able to create applications that are more efficient and easier to maintain in the long run. Furthermore, encapsulation also works hand-in-hand with polymorphism, helping developers enforce data integrity through type safety and validating user input. Together, these two concepts are essential for building reliable and secure applications.
15. How does Java ensure encapsulation?
In Java, encapsulation is achieved by creating private variables and methods that can be accessed or modified only through the class's public methods. This ensures that any changes to the data are done in a safe and controlled manner. For example, if an object contains a private field such as a name, it would be possible to control how this value is set or retrieved by providing getter and setter methods. By using these methods, the programmer can ensure that invalid data is not set and that valid data is returned in a consistent manner.
Encapsulation also helps to create more maintainable code because it prevents developers from making too many changes directly to the code. By isolating specific pieces of functionality (data and associated methods) into classes , it becomes easier to trace and debug an application.
16. Can you provide examples of design patterns that exploit OOP concepts effectively?
Sure! Some design patterns based on OOP principles include:
The Singleton Pattern is a popular design pattern used to ensure that only one instance of a class is created. This provides a global access point to the single instance, allowing it to be accessed easily throughout an application. The pattern is often used when the application requires access to a single resource such as a database connection or object for caching data.
The singleton pattern is implemented by creating a class with a method that creates an instance of the class if one does not exist yet. If an instance already exists, it simply returns the existing instance instead. This ensures that only one instance of the class can exist at any given time in an application.
The factory pattern is a creational design pattern used in software development. It is used to encapsulate the creation of objects, which can be useful when the exact class of object that will be created at runtime is not known ahead of time. This pattern defines an interface for creating an object, but leaves the details of implementation up to subclasses.
At its core, the factory pattern provides a way to decouple the process of creating objects from the logic that uses them. The client code does not need to worry about which actual class will be instantiated; instead, it should simply call a method or pass specific parameters that are then handled by a factory.
The main benefit of using this pattern is that it provides a level of abstraction between the client code and the actual implementation of the class, making it easier to maintain and extend. This pattern also makes it easier to switch implementations of an object without needing to make changes in the client code.
The Decorator Pattern is a design pattern that allows behaviors to be added or modified to an individual object at runtime, without changing the behavior of other objects of the same class. This pattern involves creating a wrapper class which wraps around the original object and adds additional functionality.
The decorator pattern can be used to create flexible designs and increase code reusability. It also makes it easier to add new features since they are implemented as separate classes rather than within existing code. The pattern makes use of composition rather than inheritance, which helps maintain a clean separation between different parts of the system and improves extensibility.
The Observer Pattern is a powerful software design pattern that enables an object (the subject) to maintain an internal list of dependents (observers) and to notify them of any state changes. This pattern allows for the decoupling of objects - one object does not need to know the implementation details of another in order to interact with it.
When the subject's state changes, it notifies its observers by calling their update methods. The observer can then query the subject for information about what has changed, or act accordingly. This is useful when there is a one-to-many relationship between objects: when one object changes, all others that are interested in it must be notified.
The Observer Pattern is especially useful when an application needs to be able to respond quickly and reliably to changes in its environment. This pattern is also used when an object needs to maintain consistency between itself and other objects without needing to know the details of their implementations.
17. What is method overloading and method overriding? How are they different?
Method overloading and method overriding are two terms in object-oriented programming that describe the ability to create multiple methods with the same name but different parameters.
Method overloading occurs when a programmer creates multiple versions of a single method, each with different numbers or types of parameters. When this happens, the compiler can determine which version of the method should be used during execution by examining the number and type of parameters passed into it. This makes it possible for a single method to serve many purposes, depending on which version is being called.
Method overriding occurs when an inherited class replaces one or more methods of its parent class with its own implementation of those methods. This is done so that subclasses can specialize functionality without having to re-im plement all of the parent class’s methods.
The two concepts are different because method overloading is used to create multiple versions of a single method, while method overriding is used to replace existing methods with subclasses’ implementations.
18. Why can't we instantiate an interface or an abstract class in Java?
In Java, interfaces and abstract classes are used to define the structure of a program without providing any implementation details. This means that interfaces and abstract classes cannot be instantiated since there is no concrete definition of how they should work. When an interface or an abstract class is used in a program, it needs to be implemented by a concrete class which provides the actual logic for the interface or abstract class methods. The concrete class can then be instantiated and used as needed.
19. Explain the concept of "composition" in OOP with an example.
Composition is a design principle where a class comprises references to other classes instead of inheriting from them. It represents a "has-a" relationship. For example, a Car class can have a reference to the Engine class instead of inheriting from it, signifying that a car "has an" engine.
20. What is the significance of the super keyword in Java?
The super keyword in Java has two primary uses.
The first use of the super keyword is to access members of a base class from within a derived class. This includes constructors, variables, and methods. For example, if we have a subclass called SubClass which extends the superclass SuperClass, then invoking the method printMethod() from SubClass can be done with "super.printMethod()".
The second use of the super keyword is to invoke an overridden method from its parent class. For example, if we override the printMessage() method in SubClass with our own implementation, we can still call the original implementation that exists on SuperClass using "super.printMessage()".
In general, the super keyword is used to access members of a superclass from within a subclass, either to invoke overridden methods or to access base class members.
Java Collections Framework
The Java Collections Framework (JCF) is a set of interfaces and classes that are used to store, manage, and access data structures in the Java programming language. Based on the principles of generics, it allows developers to create robust and reusable code. The JCF includes a variety of implementations such as lists, sets, queues, and maps. It also provides algorithms for sorting, searching, and manipulation of collections. The JCF provides extra functionality such as concurrent collections for improved performance.
At its core, the JCF is composed of four main components: collection interfaces (Collection, List, Set), map interfaces (Map), iterator interfaces (Iterator) and algorithms (sort(), search()). All of these components have been designed to work together in a unified way.
21. Explain the core differences between List, Set, and Map interfaces in Java.
List is an interface in the Java Collections Framework. It represents an ordered collection of elements in which duplicate values are allowed. List implementations typically provide a set of methods for manipulating the elements, such as add(), remove(), get() and contains().
The most commonly used implementations of the List interface include ArrayList, LinkedList, and Vector.
- 1ArrayList is an implementation of the List interface that uses an array to store its elements. It allows for fast retrieval and modification of elements, as well as dynamic resizing.
- 2LinkedList is another implementation of the List interface that uses a doubly-linked list under the hood to store its elements. It provides constant time performance for insertion and removal of elements at any given position in the list.
- 3Vector is a legacy implementation of the List interface that was introduced before Java 5. It synchronizes all operations on the list using an internal lock, making it thread-safe. However, this synchronization can lead to performance issues in multi-threaded applications, so it is not recommended for use in modern applications.
A Set is an unordered collection of unique elements, meaning that the same element cannot appear more than once in a Set. There are three main types of Sets: HashSet, LinkedHashSet, and TreeSet.
- 1HashSet is the most commonly used implementation of a Set and provides fast access to elements as well as efficient insertion, deletion, and lookup operations. A HashSet uses a hash table to store its elements which makes it very efficient for these operations. The order of iteration over the elements in a HashSet is not predictable nor is it guaranteed to remain constant over time.
- 2LinkedHashSet is similar to a HashSet except that the order of iteration over the elements in a LinkedHashSet will be the same as the order in which they were inserted. This makes it slightly slower than a HashSet for insertion, deletion, and lookup operations but faster for iteration.
- 3TreeSet is an implementation of a Set that uses a TreeMap to store its elements and guarantees that the elements will be sorted according to their natural ordering. It is slower than a HashSet or LinkedHashSet for insertion, deletion, and lookup operations but faster for iteration.
Maps are important data structures in Java which represent a collection of key-value pairs. Maps allow quick look up of values based on a key, and are commonly used to store information such as user preferences or configuration settings. Maps are part of the java.util package and contain many different implementations, each with its own set of features.
- 1HashMap is the most commonly used implementation and stores elements in an unordered fashion using a hash table. It is not synchronized and so does not guarantee thread safety.
- 2LinkedHashMap is similar to HashMap but it maintains insertion order, meaning that elements will be iterated over in the same order as they were added to the Map.
- 3TreeMap stores key-value pairs in ascending order according to their natural ordering and provides guaranteed log(n) performance for insertion, deletion, and lookup operations. Hashtable is a legacy implementation of Map that is synchronized and so is thread-safe, but it does not allow null keys or values.
22. How does a HashSet ensure uniqueness? How is it different from a TreeSet?
A HashSet is a collection that stores unique elements in an unordered manner. It uses the hashCode() and equals() methods to ensure that each element added to the Set is unique. When an element is added, it calculates its hashCode and checks for existing elements with the same value. If there are no matches, it adds the element to the Set.
HashSets do not guarantee order like TreeSets do, so they are faster but less reliable when it comes to finding specific values. TreeSets store their elements in a sorted order according to their natural ordering or a custom comparator supplied by you. This makes them more useful for searching and retrieving specific elements, since you can directly access any element from within the tree.
23. Why would you choose an ArrayList over a LinkedList or vice versa?
ArrayList is an implementation of the List interface, which is a part of the Java Collection Framework. ArrayList provides random access to its elements because it stores them in an array data structure internally. This makes it possible to retrieve any element in constant time by using its index. It also supports adding, removing and modifying elements from any position in the list with relatively efficient performance.
However, insertion and deletion operations in the middle of the list are not as efficient since all elements after those positions need to be shifted accordingly. For this reason, ArrayLists are better for operations which involve indexing and random access rather than frequent insertions or deletions.
LinkedList is a doubly-linked list implementation of the List interface. It is a linear data structure that provides better performance when inserting or deleting elements at the beginning or middle, as compared to an ArrayList. This is because LinkedList does not need to shift any elements when adding or removing items from the list. Inserting and deleting elements in a LinkedList also takes constant time, regardless of the size of the list.
However, due to its linked structure, accessing elements in a LinkedList is slower than accessing elements in an ArrayList. This is because it requires linear traversal through each node in order to get/set an element, while ArrayLists can access any element instantaneously since all of its elements are stored in a contiguous block of memory.
24. How do you safely iterate over a collection while modifying it?
When modifying a collection while iterating over it, it is important to use an Iterator object. Iterators are designed specifically for traversing and modifying collections safely. The main advantage of using an Iterator is that it provides a fail-safe mechanism by allowing the removal of elements during iteration without throwing a concurrent modification exception.
When iterating over a collection, you should first obtain an Iterator object from the collection by calling the iterator() method on the Collection interface. Then, you can loop through the elements of the Collection using iterator's hasNext() and next() methods until all elements are processed. During iteration, if you need to modify or remove any elements from the collection, you should use iterator's remove() method instead of directly modifying the collection. This will ensure that the iterator's internal state remains in sync with the collection and no concurrent modification exceptions are thrown.
25. Describe the difference between Comparator and Comparable in Java.
Comparable is an interface present in the java.lang package that is used to define the natural order of objects of a class. It requires the class to implement the Comparable interface and override the compareTo() method. The compareTo() method takes a single argument, an object of same type as that of the class in which it is defined, and compares it with the current object instance using some criteria and returns a negative integer (if less than), zero (if equal to) or a positive integer (if greater than).
For example, if we have a Student class which has name and age as attributes, we can implement Comparable interface in order to define the natural order of students according to their age attribute. The Student class will then override the compareTo() method and return a negative integer if the student instance has an age less than the argument, zero if both students have the same age, and a positive integer if the student instance has a greater age.
A Comparator is an interface in the Java Collections Framework that enables us to define ordering for a collection of objects. It provides a way to order objects based on user-defined criteria, and it can be used to sort any type of object that implements the Comparable interface.
The Comparator interface defines two methods - compare() and equals(). The compare() method returns an integer value based on the comparison of two objects. If the result is negative, it means the first object is less than the second one. If it’s zero, then both objects are equal. If it’s positive, then the first object is greater than the second one.
The equals() method compares two comparators and returns true if they are equal or false otherwise.
26. Why are Map implementations not a part of the Collection hierarchy, even though they're part of the Collections Framework?
Maps are not a part of the Collection hierarchy because Maps are used to store key-value pairs and operate differently than other collections. A map does not allow for duplicate keys, while a collection can have multiple copies of the same value stored in it. Additionally, maps do not guarantee any ordering of their elements, unlike collections which have specific orderings like insertion order or sorted order. Finally, maps provide additional methods that are specific to retrieving values from key-value pairs such as get() and put(). For these reasons, it makes sense why Maps are included in the Collections Framework but are not part of the Collection hierarchy.
27. What is the difference between HashMap, LinkedHashMap, and TreeMap?
HashMap, LinkedHashMap, and TreeMap are all implementations of the Java Map interface. A Map is an object that stores associations between keys and values (key-value pairs).
- 1HashMap: HashMap is an unordered collection which does not maintain any specific order for storing elements. It uses a technique called hashing to store elements in an array format. It doesn't guarantee the order of the map will remain constant over time.
- 2LinkedHashMap: LinkedHashMap maintains insertion order by using doubly linked list. In this way it can iterate through the map in the same order as data was inserted into it. Hence, when iteration is required, LinkedHashMap should be considered instead of HashMap.
- 3TreeMap: TreeMap is a sorted map which sorts all elements in ascending order of keys according to the natural ordering of the keys or based on a custom Comparator implementation passed into the TreeMap constructor at the time of creation.
28. Explain the concept of "fail-fast" vs. "fail-safe" iterators.
The concept of "failfast" and "fail-safe" iterators refer to different approaches for managing concurrent modifications to a collection during iteration.
In the case of a fail-fast iterator, if a modification is detected while iterating over the collection, an exception is immediately thrown. This behavior is enabled by keeping track of any modifications that occur to the collection while it is being iterated upon. If any modifications are detected, the iterator knows that they are unexpected and throws an exception. The purpose of this approach is to ensure that any unexpected changes do not go undetected and lead to incorrect or erroneous results.
By contrast, a fail-safe iterator does not throw an exception when a modification occurs while it is being iterated upon. Instead, it copies the data from the collection into a separate structure and uses that to create an iterator. This approach allows modifications to be made without worrying about exceptions being thrown while iteration is occurring. The downside of this approach is that it can be slower and more resource intensive since extra memory is required for the copied structure.
29. How does the internal resizing of an ArrayList work?
ArrayLists are collections of objects stored in an array. When the size of the data stored in an ArrayList grows, it needs to be resized to accommodate additional elements. This process is known as internal resizing and involves creating a new, larger array and copying all existing values from the original array into the new one.
When ArrayLists need to be resized, they normally double their current capacity. This means that if an ArrayList's capacity is currently 10 elements, it will be doubled to 20 elements when it needs to be resized. This allows for more efficient use of memory, since ArrayLists do not have to continually grow by small increments whenever a new element is added. The downside of this approach is that resizing the ArrayList can take time, as it requires allocating a new array and copying values into it.
30. What is the ConcurrentModificationException, and when does it occur?
ConcurrentModificationException is an exception that occurs when an object is modified concurrently while it is being iterated over. This happens when the collection is changed while it is being iterated over, resulting in the iterator having a different view of the collection than its underlying data structure. It can occur when multiple threads are accessing and modifying the same collection at the same time. It can also occur if the collection is modified during iteration by calling methods such as remove() or add().
The ConcurrentModificationException can be avoided by using a synchronized collection or an unmodifiable collection. Synchronized collections allow multiple threads to access the same data, but ensure that all modifications are done in a thread-safe manner. Unmodifiable collections prevent any modifications from being made to the collection while it is being iterated over. It is important to note that both of these solutions will cause some performance overhead due to their locking mechanisms, so they should only be used when necessary.
Java Exceptions are objects that indicate the occurrence of an unexpected condition or event in a Java program. The most common type of exception is the checked exception, which indicates an error that must be handled programmatically. An unchecked exception is one that occurs at runtime and does not require handling—it will terminate the program if not caught.
When a Java program encounters an exceptional condition, it throws an exception object containing information about the error, which can then be caught by the programmer and handled appropriately. For example, when attempting to open a file for reading but failing due to insufficient permissions, a FileNotFoundException is thrown which can then be caught and handled within the program execution.
Exceptions provide an elegant way to handle errors within a program and ensure that the program continues to execute in an expected manner, even when unexpected conditions occur.
31. What is the primary difference between checked and unchecked exceptions?
The primary difference between checked and unchecked exceptions is the way in which they are handled by the compiler.
Checked exceptions are considered to be a part of the application's API, and must be explicitly declared or caught in a try-catch block. Checked exceptions are typically used to indicate an exceptional condition that the programmer can anticipate, such as a user-input error or a network timeout. These types of exceptions must be handled explicitly in order to maintain the integrity of the application and ensure proper behavior.
Unchecked exceptions, on the other hand, are more like runtime errors that do not need to be explicitly declared or caught; instead, their presence is reported by the JVM after an error occurs. Unchecked exceptions are usually errors that occur due to programming mistakes or unforeseen conditions, such as a null pointer exception or an array index out of bounds. They do not need to be caught explicitly, since they will still be reported by the JVM; however, it is recommended that appropriate measures are taken in order to handle them gracefully and prevent further errors from occurring.
32. How does the try-catch-finally mechanism work? What happens if both catch and finally blocks have a return statement?
The try-catch-finally mechanism is a way of handling errors and exceptions in Java. The try block contains the code that might throw an exception. If an exception is thrown, the catch block is executed. Finally, the finally block will always be executed regardless of whether or not an exception was thrown.
When both catch and finally blocks contain a return statement, the return value from the catch block takes precedence over the return value from the finally block. This means that any modifications made to a variable within the finally block will not be reflected in the returned value from that function. It also means that any actions taken within the catch block may affect what is ultimately returned from this method, even though they may not have been explicitly specified in the code itself.
33. Explain the hierarchy of the Java Exception classes.
The Java Exception class hierarchy is a set of classes that represent various kinds of errors and exceptions that can occur in a Java program. The topmost exception class in the hierarchy is java.lang.Throwable, which is the parent of two subclasses: java.lang.Exception and java.lang.Error.
Exceptions are conditions that can be caught and handled by an application, while errors cannot be handled in the same way and generally signify serious problems which must be addressed by the programmer or system administrator. All exceptions descend from the Exception class, while all errors descend from the Error class.
The Exception class is further broken down into two main categories: checked exceptions and unchecked (runtime) exceptions. Checked exceptions must be declared in the method signature and handled by the application. Unchecked exceptions do not need to be declared or handled, but they should still be taken into consideration when developing applications.
34. Why should you avoid using exceptions for control flow in a Java application?
Exceptions should be used only for exceptional cases, such as when an unexpected error occurs in the program or when input data is not valid. For normal control flow, other mechanisms such as if-statements and loops should be used instead. There are several reasons why this is recommended:
- 1Using exceptions for control flow can lead to code that is difficult to read and maintain. Exceptions are often seen as “exceptional” events that disrupt the regular flow of a program and require special handling; therefore, it can be hard for other developers to understand what your code does when you use them for control flow.
- 2Exceptions are expensive operations compared to normal control statements like if-statements or loops.
- 3Exceptions can hide potential bugs in your code. When you use exceptions for control flow, it is easy to forget to handle certain cases or overlook a certain condition. This could lead to unexpected errors that are hard to track down and debug.
- 4The use of exceptions for control flow can make it difficult for other developers to contribute to your codebase. As mentioned before, using exceptions for control flow can make it harder for other developers to understand what your code does which makes it harder for them to contribute properly.
35. How do you create a custom exception?
Creating a custom exception involves extending the Exception class and defining the methods for your new exception. For example, you might create a CustomException class that is used to report program errors related to user input. To do this, you would need to declare a CustomException class that extends Exception, overriding any necessary methods and adding any additional fields or methods specific to your needs. Your CustomException class should contain at least two constructors: one with no parameters and one with an instance of Throwable as its only parameter. When creating your CustomException object, you can provide an appropriate message in the constructor call or use the default no-argument constructor and then call setMessage() afterward. Finally, when throwing your CustomException, make sure to include the original cause as a parameter in the constructor call.
36. Can you catch multiple exceptions in a single catch block? How?
Yes, it is possible to catch multiple exceptions in a single catch block. This can be done using the pipe operator (|) to separate the different types of exceptions that need to be caught. For example, if you want to catch both IOException and SQLException, you can write:
37. What is the Throwable class, and how does it relate to the Exception hierarchy?
The Throwable class is the base class in Java's exception hierarchy and it represents any error or exception that can occur within a Java program. All other Exception classes in the hierarchy are subclasses of Throwable. The Throwable class contains two subclasses, Error and Exception, which contain all of the specific types of exceptions that may be thrown by a Java program. An Error indicates a serious problem that usually cannot be recovered from, while an Exception indicates a problem that can be handled by the code. The Throwable class also has methods to get information about the exception such as its message, stack trace, and cause.
38. Explain the importance of the finally block in exception handling.
The finally block in exception handling is important for ensuring that code gets executed regardless of the outcome of a try/catch block. Regardless of whether an exception is thrown, or if the try block completes successfully, the code inside the finally block will always be executed. This ensures that certain tasks will be completed no matter what happens—for example, closing resources like files and database connections. It also allows developers to separate error handling logic from implementation logic—errors can be handled at the same level as they occur while implementation logic can stay separate and unaffected. Finally blocks are essential for implementing robust exception handling practices, allowing developers to have better control over how their programs handle errors.
39. What is the "exception chaining" mechanism, and why might you use it?
Exception chaining is a mechanism used in Java to handle exceptions. It allows the programmer to “chain” multiple exceptions together, so that if one exception occurs, the other chained exceptions can be processed and handled as well. This helps developers write more organized code and better manage the flow of errors.
The main reason for using this feature is to make error handling easier by allowing nested or chained exceptions to be thrown instead of having to handle different kinds of errors separately. Exception chaining also helps in providing more detailed information about errors as it traces back the originating error that caused the chain reaction of events leading up to the current exception. This enables developers to pinpoint problems quickly, which makes debugging faster and easier. Additionally, it helps reduce code redundancy by allowing developers to use the same exception class when dealing with multiple errors.
40. How does Java's try-with-resources feature simplify exception handling?
Java's try-with-resources feature is a syntactic enhancement to the traditional try-catch block that simplifies exception handling. It allows developers to define an instance of a resource within the parentheses after the try keyword. When the code block exits, either normally or abruptly, the resources declared in this area are automatically closed. This eliminates the need to explicitly close each resource with a finally block, which can lead to unncessary boilerplate code and potential memory leaks. Additionally, any exceptions thrown by these statements can be caught and handled in a single catch block or multiple catch blocks as normal.
Java Input/Output (I/O)
Java I/O (Input/Output) is an important part of the Java language, allowing developers to read and write data from a variety of sources. With Java I/O, developers can create programs that can interact with external resources such as files, networked devices, and other applications.
Java I/O is organized into two different APIs: the legacy java.io package and the newer java.nio package. Both packages provide multiple classes which allow developers to read from and write to a wide range of sources. The java.io package provides access to character streams (Reader/Writer classes) as well as binary streams (InputStream/OutputStream classes). The java.nio package offers even more features such as buffer reading/writing, non-blocking I/O, and memory-mapped files.
41. Differentiate between byte streams and character streams in Java I/O.
In Java I/O, there are two types of streams: byte streams and character streams.
Byte streams provide a convenient means for handling input and output of 8-bit bytes while character streams provide a convenient means for handling 16-bit Unicode characters. Byte Streams are used to read data from or write data to binary files such as images, audio clips, etc. The classes that are related to byte stream are FileInputStream and FileOutputStream.
Character Streams are used to read data from or write data to text files such as configuration files, program code, etc. The classes that are related to character stream are FileReader and FileWriter.
The main difference between the two is the type of data they handle. Byte streams handle data in the form of 8-bit bytes whereas character streams handle 16-bit Unicode characters.
42. How do you use the BufferedReader and BufferedWriter classes in Java?
BufferedReader and BufferedWriter are used for reading and writing text, providing buffering to improve performance.
BufferedReader is used for reading text from a character-input stream. To create a BufferedReader, you must wrap an existing Reader into it.
BufferedWriter is used for writing text to a character-output stream, providing buffering to improve performance. To create a BufferedWriter, you must wrap an existing Writer into it.
43. What is the importance of the Serializable interface in Java?
The Serializable interface is a marker interface in Java which provides a standard for serializing objects. It tells the JVM that this object can be serialized, so the JVM is able to transform the object’s state into a byte stream and vice versa. This is useful for transferring data over networks, writing data to files, or caching objects in memory.
Serialization is also important for distributed applications because it enables components running on different machines to communicate with each other by exchanging objects. Furthermore, it makes it easy to create fault-tolerant systems since an object's state can be saved and then restored if needed.
In order for an object to be serialized, all of its members must also be serializable. If any of the members are not serializable, then the entire object cannot be serialized.
44. Explain how the File class works in Java.
The File class in Java is a class used for interoperability with the file system. It provides several methods to create, delete, and manipulate files as well as directories. This class can also be used to read and write data from a file. The File class supports basic file operations such as reading, writing, and deleting of files.
When creating a File object, it requires a pathname string that specifies the absolute path of the file or directory. The pathname should include both the full directory name and filename. With this information, the appropriate platform-specific file handling mechanism is created and used for further operations on the specified file or directory.
In addition to creating Files objects, some useful operations can be done using this class. These include creating files, deleting files, getting file information (name, size, date last modified etc.), reading and writing data to a file, and much more.
45. Describe the process of object serialization and deserialization.
Object serialization is the process of converting an object into a sequence of bytes that can be stored in a file or transmitted over a network. It enables objects to be saved and retrieved without having to write code for each individual object type separately. Deserialization is the opposite process, which converts the sequence of bytes back into an object.
The process of serializing an object involves breaking it down into its essential components, such as its attributes and their values, along with any associated methods and data structures. All this information is then stored in a stream of bytes, also known as a "serialized form". The actual format of the serialized form depends on the language used for the serialization, but typically includes some combination of binary and textual data.
When the serialized object is needed again, the stream of bytes is retrieved and the deserialization process reverses the steps taken during serialization. The values of each attribute are reconstructed from the serialized form and used to create a new instance of that object. This newly created instance is then returned to the user.
46. How can you prevent a field from being serialized?
One way to prevent a field from being serialized is to declare it as transient. A transient variable is one that will not be included when an object is serialized. This can be done by declaring the field with the keyword transient, like so:
private transient int myField;
This tells the Java compiler that the field should not be included in the process of serializing and deserializing an object. It's important to note that this only works for primitive data types (such as int, double, etc.), and not for reference data types such as Objects or Arrays.
47. What is the difference between FileReader and InputStreamReader?
The main difference between FileReader and InputStreamReader is that FileReader is a convenience class for reading character files, whereas InputStreamReader is used to read bytes and then convert them into characters.
FileReader reads directly from a file, while InputStreamReader takes an existing input stream such as a FileInputStream or SocketInputStream and converts it into characters. The other major difference between the two classes is that they use different types of character encoding. FileReader uses the system's default encoding while InputStreamReader can be constructed with any supported character encoding.
48. How do you handle character encoding issues in Java I/O?
When dealing with character encoding issues in Java I/O, the best approach is to be explicit about the character encoding that you are using. Java provides a number of classes for handling input and output streams, which allow you to specify the character encoding used for reading and writing data.
For example, if you are working with files, then you can use the FileReader and FileWriter classes to read and write files with a specified character encoding. If you are working with network communication then InputStreamReader and OutputStreamWriter classes can be used to specify the desired character encoding when communicating over a network.
Additionally, it is important to remember that some resources may have their own default character encoding settings (such as databases or web containers), and it is important to be aware of these settings in order to ensure that the expected character encoding is being used.
49. Describe the StandardCharsets class and its significance.
The StandardCharsets class is a Java SE 8 class which provides constant definitions for the standard charsets supported by many Java implementations. It is part of the java.nio.charset package and provides access to various constants like UTF-8, UTF-16 and ISO-8859-1. These constants can be used in order to decode or encode strings with specific character encodings. The StandardCharsets class also provides static methods for obtaining a charset from its canonical name or alias.
The significance of the StandardCharsets class is that it simplifies character encoding in Java applications, as it makes it easier to work with multiple charset encodings within the same application. This class eliminates the need to manually look up and specify character encodings as it provides direct access to the predefined constants.
50. How does the Scanner class simplify input operations in Java?
The Scanner class simplifies input operations in Java by providing an easy-to-use and efficient means to extract data from various sources such as the keyboard, files, and strings. The Scanner class provides a number of methods to read different types of data such as ints, doubles, longs, floats, booleans, and strings. These methods make it much easier for programmers to quickly read in the necessary data from a source into their program.
For example, the nextInt() method can be used to quickly read an int value from a given source and assign it to an int variable. Similarly, the nextLine() method can be used to quickly read a line of text from a given source and assign it to a String variable. This makes it much easier for developers to quickly get the data they need from a given source, without having to worry about parsing and formatting the data themselves.
Java Threads and Concurrency
Java Threads and Concurrency allows developers to write efficient programs that can execute multiple tasks at the same time. This is especially useful when dealing with large amounts of data, where doing many tasks in parallel can save time and resources.
Threads are a way to handle multiple tasks at once in Java. A thread is like a mini program within a program that runs independently from the main program. Threads are lightweight processes, meaning they take up less memory than other types of processes, allowing them to run faster and more efficiently than other methods. In addition, threads can communicate with each other to coordinate their behavior and perform complex operations.
Concurrency refers to the ability for multiple threads to share access to data and resources while still maintaining the integrity of that data. This is achieved by using synchronization techniques, such as locks, mutexes, and semaphores which help to ensure that multiple threads access and modify data in a safe manner.
51. How do you create a thread in Java? Explain both ways.
Java provides two primary ways to create a thread:
By extending the Thread class, we can create our own custom thread implementation. This allows for a more customizable threading experience that is tailored to the needs of the user. Additionally, extending the Thread class makes it easier to control and monitor multiple threads within an application.
The most common way to extend the Thread class is by overriding its run() method. The run() method is where all of the code for the thread will be executed, and so any logic or processing you wish to implement should go inside of this method. Other methods can also be overridden in order to customize how each thread behaves, including start(), stop(), suspend(), resume(), and interrupt().
By implementing the Runnable interface, a Java class can provide an entry point for separate threads of execution. This allows multiple tasks to run concurrently within a single process, which can improve application performance and scalability. It is especially useful when there are many tasks that need to be performed in parallel, since each task can be run on its own thread instead of having to wait for other tasks to complete.
To implement the Runnable interface, a class must override its run() method. This method should contain the code that needs to be executed by the new thread and will typically include a loop or series of instructions that are repeated until some condition is satisfied (e.g., until all items in a list have been processed). The run() method can also call other methods in order to complete its task.
52. Describe the differences between the wait(), notify(), and notifyAll() methods.
The wait(), notify(), and notifyAll() methods are important elements of the Java language used for inter-thread communication. They are all methods of the Object class, which are therefore available to all objects in Java.
- 1The wait() method causes the current thread to wait until another thread invokes either the notify() or notifyAll() method. The current thread must own the object's monitor to use this method.
- 2The notify() method wakes up a single thread that is waiting on this object's monitor. It randomly chooses one of the waiting threads and gives it exclusive access to the object’s monitor.
- 3The notifyAll() method wakes up all threads that are waiting on this object's monitor. It not ifies all threads waiting for the monitor, allowing each thread to compete for exclusive access.
53. What are the dangers of thread starvation and deadlock?
Thread starvation and deadlock are two of the most common dangers that can occur when working with threads in a multi-threaded application. Thread starvation occurs when one or more threads are unable to access resources they need in order to complete their tasks, while deadlock is a situation where two or more threads become stuck waiting for each other to release a resource.
Thread starvation can lead to performance issues since the thread may be continuously blocked from accessing its required resources, causing it to take longer than necessary to complete its task.
Deadlock can cause entire applications or sections of applications to stop running entirely as all threads become stuck waiting for each other. To prevent thread starvation and deadlock, developers should ensure that any shared resources are managed carefully and that threads are given access to them in an orderly manner.
54. How do synchronized blocks in Java work?
Synchronized blocks in Java work by preventing more than one thread from executing the same code block simultaneously. When a thread enters a synchronized block of code, it acquires the monitor lock associated with the object referenced in the synchronized statement. The monitor lock ensures that only one thread can execute the code at a time. Once the thread has completed execution of the synchronized block, it releases its lock on the object and other threads can then enter their own synchronized block for that object.
The use of synchronized blocks is an important part of multi-threaded programming in Java and helps to prevent race conditions, deadlocks, and other undesirable behavior. For example, if two threads try to update a shared resource at the same time without proper synchronization, the two threads may end up overwriting each other's changes.
55. Explain the concept of thread-local variables and their usage.
ThreadLocal variables provide a way to store and retrieve data at the thread level, instead of having to rely on global or instance variables. This is especially useful when working with threads in multi-threaded applications. One common use case is when each thread needs its own copy of an object as opposed to sharing one object among all threads.
ThreadLocal variables are created using the ThreadLocal class, which provides get() and set() methods for storing and retrieving data associated with the current thread. When you call the get() method on a ThreadLocal variable, it returns the value associated with the current thread, while calling set() assigns a new value to that same thread. ThreadLocal variables do not share their values between different threads; each thread has its own copy of the data.
56. Describe the Executor framework in Java.
The Executor framework in Java is a lightweight, high-performance task scheduling framework that enables developers to perform asynchronous tasks. It provides an abstract layer over the thread pooling mechanism and helps developers manage the lifecycle of threads.
With the Executor framework, developers can assign one or more threads to run a single task or multiple tasks in parallel. The Executor framework simplifies the coding process for running concurrent tasks by providing APIs for creating and managing threads. It also provides support for scheduling recurring tasks and for managing resources such as thread pools. In addition, the Executor framework provides methods for monitoring progress and managing errors during execution.
57. What's the difference between the Callable and Runnable interfaces?
The Callable and Runnable interfaces are both used for executing tasks in a concurrent environment. The main difference between the two is that Callable returns a result of type Future, while Runnable does not. Additionally, Callable can throw a checked exception, whereas Runnable cannot.
Runnable is intended to represent an action that does not return any data, while Callable is intended to represent an action that returns data. When a thread runs a Runnable task, it does not return any value upon completion of the task. On the other hand, when a thread runs a Callable task, it returns an object of type Future which allows access to the result of computation when it becomes available.
58. How does a CountDownLatch work?
A CountDownLatch is a synchronization mechanism used in concurrent programming to allow one or more threads to wait for a certain number of events to occur before continuing. It works by having a counter maintained by the CountDownLatch object. When the latch is initialized, it is set to a specified number (the count). Each time an event occurs, the count is decremented. When the count reaches zero, all waiting threads are released and can continue their execution.
CountDownLatches are often used when multiple threads must wait for each other before making progress, such as when one thread needs to wait for another thread to finish initializing resources before it can proceed with its own initialization. The main advantage of using a CountDownLatch is that it allows the creation of threads in an ordered, synchronous fashion.
59. Explain the difference between CyclicBarrier and Semaphore.
A CyclicBarrier is a synchronization construct that allows multiple threads to wait for each other to reach a common barrier point. It can be thought of as a meeting point for multiple threads, where they must gather before moving on to the next stage. A Semaphore is an object used to control access to a shared resource by multiple threads. It allows one thread at a time to have exclusive access to the resource, and it can limit the number of threads that can access the resource at any given time.
The main difference between CyclicBarrier and Semaphore is that CyclicBarrier’s purpose is for synchronizing all threads so they reach a certain barrier point before continuing their execution, while Semaphore ’s purpose is for regulating access to a shared resource by multiple threads.
60. What are thread-safe collections, and when should you use them?
Threadsafe collections are a type of Java collection that is safe to use in a multithreaded environment. They are designed to ensure that all operations performed on the collection will be thread-safe and will not result in data inconsistency or race conditions. Threadsafe collections provide a way for multiple threads to access and modify the data stored within them in a synchronized manner. These collections typically have methods that can lock the entire collection while one thread is accessing it, thus ensuring that no other threads can access or modify the data until the first thread has finished its operation.
When should you use threadsafe collections? Threadsafe collections are useful when you need to share data between multiple threads but still want to maintain consistency and avoid race conditions. This could be for tasks such as updating a database or processing a queue of tasks.
Java Memory Management
Java Memory Management is the process of allocating, deallocating, and managing memory within a Java application. This involves both the Java Virtual Machine (JVM) as well as native code running on the underlying operating system. Memory management plays an important role in the performance and stability of any Java application.
Java Memory management is based on two components: Heap and Stack memory. Heap memory is used to store objects created by the application while Stack memory stores only primitive data types like int, float etc. Heap memory can be further divided into Young Generation (Eden space + 2 Survivor spaces), Old Generation or Tenured Space and Permanent Space which includes class metadata and other internal JVM structures. Each area has its own garbage collection algorithm. Proper memory management and garbage collection tuning is necessary to ensure application performance and stability.
61. Describe the differences between stack and heap memory.
Stack is a special area of memory used during the execution of threads, storing local variables and method call information. It is organized into frames, with one frame for each method invocation. This structure ensures that when a thread is executing a method, it has access to all of the values stored in the associated frame.
The stack is faster than other memory locations like the heap because it does not need to search for empty spaces or worry about fragmentation. However, unlike the heap which can grow arbitrarily large, the stack has a limited size and cannot expand beyond that limit. When this limit is reached, an overflow exception will be thrown and the thread will terminate.
Heap is a region of memory where objects are allocated. It's managed by the garbage collector and is larger than the stack, but slower in allocation and retrieval.
When an object is created, it's placed in the heap. As objects are no longer referenced, they become eligible for garbage collection and are eventually removed from the heap. The size of the heap depends on the JVM’s configuration parameters such as -Xms and -Xmx which set the initial and maximum heap size respectively.
The main benefit of using a heap structure is that it enables fast allocation of memory to objects, allowing them to be quickly created and destroyed without having to worry about fragmentation or running out of memory. Because memory management is handled by the garbage collector, it also reduces the burden of memory management from developers.
62. How does garbage collection work in Java?
Garbage collection in Java is a form of automatic memory management. The Java Runtime Environment (JRE) uses garbage collection to reclaim the memory used by objects that are no longer referenced by the program so that this memory can be reused. Garbage collection works by periodically running an algorithm that searches for objects on the heap that are no longer referenced by the program and then reclaims their memory.
The JRE contains a garbage collector which runs as a low-priority thread in the background. When the garbage collector runs, it first identifies all objects on the heap which are no longer being used by the program. It then reclaims their memory, marks them as free space, and makes them available for reallocation. This process of locating and reclaiming unused memory is known as garbage collection.
63. Explain the concept of strong, soft, weak, and phantom references.
A strong reference is the most commonly used type of object reference in Java. It is a direct pointer from the referring variable to the referenced object. As long as a strong reference exists, the referenced object will not be eligible for garbage collection. This means that any strongly-referenced objects must eventually be explicitly deallocated by setting the reference to null or reassigning it to another (strong) reference.
Strong references are used throughout Java code and are an essential part of proper memory management. They are usually created when creating a new instance of an object, using the new keyword, and serve as the primary way of accessing any given object in memory.
Soft references are a type of reference object in Java that allow objects to be garbage collected in low-memory situations. They are implemented using the java.lang.ref.SoftReference class. Soft references are typically used when an application needs to cache data and maintain a soft reference to it, so that it can be discarded if needed when memory is low.
Unlike strong references, which prevent an object from being garbage collected, soft references will only do so under certain conditions. Specifically, if the JVM is running low on memory and needs to reclaim some resources, then the JVM may decide to collect softly referenced objects as part of its Garbage Collection process. This makes soft references ideal for creating caches where information can be stored temporarily until it is no longer needed.
A weak reference is a special type of object reference in Java that doesn't prevent the object from being garbage collected. Weak references are used to refer to objects that might not need to be explicitly referenced, such as for caching purposes.
Unlike strong references, which maintain an active reference to an object and keep it from being garbage collected, weak references allow an object to be garbage collected if no other references exist. When a garbage collection cycle occurs, the weakly referenced objects are marked for collection and then removed from memory. This can help reduce the memory footprint of applications when objects are no longer actively being used but still need to remain accessible for certain operations or activities.
Weak references can be especially useful when dealing with large images or other objects that take up a significant amount of memory. By using weak references, these objects can be retained in memory but still be eligible for garbage collection when no longer needed.
A phantom reference is a type of object reference in Java that allows you to determine precisely when an object was collected. It is designed for use in postfinalization actions and is implemented using the java.lang.ref.PhantomReference class.
A phantom reference is different from a regular or strong reference in that it has no effect on garbage collection; objects referenced with a phantom reference will still be eligible for garbage collection by the JVM when they are no longer strongly referenced anywhere else in the program. This makes them ideal for performing postfinalization actions, such as closing resources like files or databases, after an object has been cleaned up by the garbage collector.
When an object that is being tracked by a phantom reference is collected, the phantom reference will be enqueued in a special "reference queue". This allows an application to detect when an object has been collected and take any necessary postfinalization actions without having to maintain a strong reference to the object.
64. What are memory leaks, and how can you detect and prevent them in Java?
Memory leaks occur when an application allocates memory but fails to release it after it is no longer needed. This can eventually lead to the application running out of memory and crashing. Memory leaks can be difficult to detect and prevent in Java due to its garbage collection system.
To detect a memory leak, developers should regularly monitor their application’s memory usage. If they notice that the amount of allocated memory is steadily increasing over time, then there is a good chance that there is a memory leak. To further confirm that a leak exists, the developer can use tools such as jmap or jhat to take heap dumps of the application and analyze them for potential causes of the leak.
To prevent memory leaks in Java, developers should ensure that any objects allocated to memory are properly released when they are no longer needed. This can be done by using the appropriate language features for memory management, such as try-with-resources or WeakReference. Additionally, developers should use profiling tools to monitor the behavior of their application and detect potential sources of memory leaks.
65. Describe the different garbage collection algorithms in the JVM.
Several GC algorithms are employed by the JVM:
The Serial Collector is a garbage collection algorithm that is used in Java Virtual Machines (JVMs) to manage memory. It uses a single thread for its garbage collection tasks, making it well suited for applications which are single-threaded.
The main advantage of the Serial Collector over other algorithms is that it requires less time to complete its task. This makes it suitable for applications which require very low pause times, as the garbage collection process will not cause any performance issues. Furthermore, the amount of memory required by the Serial Collector is fairly small compared to other garbage collectors.
However, the main disadvantage of this algorithm is that it can be inefficient with multi-threaded applications as only one thread can be used at any given time.
Parallel Collector (Throughput Collector)
The Parallel Collector, also known as the Throughput Collector, is a garbage collector in the Java Virtual Machine (JVM). It uses multiple threads to improve garbage collection performance, and is best suited for applications that are heavily multithreaded.
The Parallel Collector runs in parallel with application threads and performs minor and major collections. Minor collections are used to collect unreachable objects from young generations (eden, survivor spaces), while major collections target the old generation. The Parallel Collector will employ multiple threads during these collections to improve performance by distributing the work among them.
The more threads employed, the faster the garbage collection process should be. However, it can also lead to an increased overhead because of context switching between different threads. Therefore , the Parallel Collector should be used with caution in applications where pause times are critical.
CMS (Concurrent Mark-Sweep)
CMS (Concurrent MarkSweep) is a garbage collection algorithm used for Java Virtual Machine (JVM). It is designed to minimize application pause times by doing most of its work concurrently with application threads. CMS works in two phases, the first is the "mark phase" and the second is the "sweep phase".
In the mark phase, CMS marks all live objects using a concurrent thread. During this time, application threads can still access objects while being marked. This reduces or eliminates long pauses caused by stop-the-world events, which allows applications to run more smoothly without significant delays.
In the sweep phase, CMS reclaims memory occupied by dead objects that were previously marked in the mark phase. It also compacts the heap by moving objects and adjusting references accordingly.
CMS is suitable for applications with low latency requirements that need to minimize pause times caused by garbage collection. However, it can be challenging to tune CMS for different workloads as it requires a deep understanding of how the algorithm works.
G1 (Garbage First)
G1 (Garbage First) is a garbage collection algorithm that was introduced in Java 8. It is intended to replace the older CMS (Concurrent Mark Sweep) algorithm, and it is intended for multithreaded applications with a large heap. G1 works by dividing the heap into individual regions, each of which stores objects that are related to each other in some way. With G1, memory can be allocated into respective regions as needed in order to minimize object movement and fragmentation when performing garbage collection operations.
When performing garbage collection, G1 will concentrate on collecting garbage from the regions that have the most garbage first. This allows for better overall performance since it allows for more efficient use of available resources such as CPU and RAM . This also reduces the amount of time spent on garbage collection since it focuses on only those regions that have the most garbage.
G1 is a better option for applications with large heaps and multiple threads. It can offer improved performance and throughput compared to CMS, as long as it is properly tuned for the specific workloads involved.
66. How can you manually request garbage collection for an object?
Garbage collection is an automated process in Java which reclaims memory from objects that are no longer needed. While the garbage collector is usually triggered automatically by the JVM, you can also manually request garbage collection for an object. To do this, you must call the System.gc() method.
This will request that the JVM run the garbage collector on all eligible objects and free up any unused memory. It is important to note that calling System.gc() does not guarantee that garbage collection will occur since the JVM may delay or ignore the request depending on its current state.
67. What is the Java memory model?
The Java Memory Model (JMM) is a specification that describes how the java virtual machine works with the computer's memory. It specifies how and when different threads can see values written to shared variables by other threads, and how to ensure that all threads see a consistent view of memory. The JMM also defines happens-before relationships, which guarantee that certain operations occur before others. These guarantees enable Java programs to execute correctly in a multithreaded environment, even on architectures with weak memory models.
The JMM also defines a set of operations that are safe for use in a concurrent environment. These operations include Volatile variables, Atomic variables, and Locks. Volatile variables are used to ensure that all threads see the most up-to-date value of a variable even if it is modified by another thread. Atomic variables allow different threads to modify the same variable without conflicting with each other. Locks are used to ensure that only one thread can access a certain section of code at any given time.
68. Explain the significance of the volatile keyword.
The volatile keyword in Java is used to denote that a field is not subject to normal threading optimizations, and should always be accessed directly from main memory. This means that any threads accessing the variable will always get the most up-to-date value of the variable. Without the use of this keyword, it is possible for a thread to cache a copy of a variable in local memory, and never read or write back to main memory, thus effectively using an outdated version of the variable.
The most common use case for volatile variables are atomic flags and status indicators - such as when two threads must communicate with each other without explicit synchronization primitives. By setting a volatile boolean flag between threads, one thread can indicate that an event has occurred, and the other thread can check this flag in a loop until it is true.
It is important to note that volatile variables do not guarantee any type of mutual exclusion or synchronization - they only guarantee that each thread will always see the most up-to-date value. For more advanced synchronization requirements, other techniques such as locks must be used.
69. How can you tune the JVM's garbage collector?
Tuning the JVM's garbage collector requires a thorough understanding of how the JVM manages its memory. When the JVM is running, it allocates memory to objects and then reclaims any memory that is no longer being used. The garbage collector (GC) is responsible for this process. To optimize the performance of your program, you should tune the GC settings to make sure it is running efficiently.
- 1Adjusting Garbage Collection Pauses: One way to tune the GC is by adjusting how long it pauses after collecting an object. You can use a flag like -XgcPause to specify how long the GC should pause before continuing with collection. This will help reduce pauses when running intensive tasks, but needs to be adjusted based on the type of workload.
- 2Adjusting Heap Size: The heap is the area of memory where objects are allocated. The default size of the heap can be too small for some applications, resulting in frequent garbage collection activities and pauses. You can increase the heap size by using flags like -Xmx or -verbosegc.
- 3Selecting a Garbage Collector: The JVM comes with several different garbage collectors that have different characteristics and performance implications. For example, the CMS collector is usually better at reducing application pauses while the G1 collector is often more efficient at reclaiming memory overall. You can select a specific garbage collector to use with the -XX:+UseGC flag.
70. What's the difference between OutOfMemoryError and StackOverflowError?
OutOfMemoryError is a type of error thrown by Java Virtual Machine (JVM) when it cannot allocate object memory, indicating that the heap is exhausted.
This can be caused by an excessive number of objects being created, or they could be too large for the JVM to handle. If this happens, the JVM will throw an OutOfMemoryError to alert the user that the application has run out of memory and needs more allocated to it in order to run properly.
The most common cause of OutOfMemoryError is when the amount of available heap memory has been exceeded and there are no more objects that can fit into it. This can happen if too many objects are created or if those objects are very large in size.
The StackOverflowError is an error thrown in Java when a thread's stack size has been exhausted, typically due to excessive recursion. When this error is thrown, it usually indicates that the program is trying to execute too many nested method calls, and the amount of memory allocated for the thread’s stack is not enough to handle the number of calls being made.
Java Language Features and Enhancements
One of the most important features of Java is its platform independence. This means that programs written in Java can run on any operating system or device with a compatible virtual machine (JVM). This makes Java highly versatile and useful for developing programs for different platforms.
Another notable feature of Java is its Automatic Memory Management system. This system helps to manage memory usage more efficiently by automatically reclaiming memory from unused objects. This helps to reduce memory leaks and optimize performance.
Java also includes a wide variety of language features that make it easy to write clean, robust code quickly and efficiently. These include static type checking, generics, annotations, lambda expressions, and more.
71. How do generics improve type safety in Java?
Generics allow types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. This enables programmers to create generic algorithms that can work for multiple types while providing type safety at compile time. This makes it possible to catch errors in the code earlier on in the development process, before even running the program.
Generics also make it easier to write clean code that is more efficient and reusable as it does not require casting objects or checking types of objects manually.
With generics, type safety is enforced by the compiler which allows developers to identify potential mistakes early on, reducing debugging efforts later on in the development cycle.
72. Describe the function and usage of lambda expressions.
Lambda expressions are a feature of Java 8 that allow developers to write more concise and readable code. They provide an easier way of writing anonymous functions, which can be used to define classes or methods without requiring the use of an extra class or interface. Lambda expressions are also known as "closures" or "anonymous functions".
A lambda expression is composed of a parameter list, an arrow token (->) and a body. The parameter list is a comma-separated list of variables that you want to pass into the lambda expression. The arrow token separates the parameters from the body of the expression, which contains the logic to execute when the lambda expression is invoked. The body can contain any valid code, including assignments, loops, and other expressions. Lambda expressions are commonly used for sorting collections, such as lists or maps, or performing operations on streams of data.
73. What are Java annotations, and how are they different from comments?
Java annotations are special types of comments that provide data about a program. They have no direct effect on the operation of the code they annotate. Annotations can be used to provide additional information about the code, such as its author, its purpose, or other meta-data.
Annotations are different from comments in that they are not meant to be read by humans. Instead, they are intended for use by tools and frameworks like compilers and libraries. For example, many Java frameworks use annotations to define how certain elements should be interpreted or handled at runtime. Additionally, annotations can contain elements such as name-value pairs which further define their purpose and meaning.
Annotations can also be used to generate source code from existing code , such as creating getters and setters from fields.
74. Explain the Stream API and its benefits.
The Stream API is a set of classes and interfaces in the Java 8 SDK that allow developers to create and manipulate streams. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result. Each element in the stream is processed in parallel or sequentially, and then combined into a single result.
The Stream API provides many benefits over traditional collection-based programming techniques, including improved performance, better readability, and increased flexibility. The Stream API allows for efficient bulk operations on collections without needing to write complex loops or use external libraries. It also enables functional-style programming, allowing developers to express computations with fewer lines of code than traditional procedural approaches. Additionally, the Stream API helps reduce boiler plate code by automatically handling resource management, exception handling, and parallelism.
75. What is the diamond problem in OOP, and how does Java resolve it?
The diamond problem is a term used in computer science to describe the multiple inheritance or virtual inheritance issue that arises when two classes have a common base class and both are inherited by another class. This creates an ambiguity about which implementation of the methods from the common base class should be used in the derived class. In Java, this problem is resolved by using what is known as "Java Interfaces".
Interfaces provide a way for different classes to share methods without creating any ambiguity. An interface defines a set of abstract methods that must be implemented by any concrete subclass; they do not contain implementation code, only method signatures. By implementing an interface, one can guarantee that all implementations will provide the same behavior and thus there can be no conflict between multiple implementations.
76. How does the var keyword introduced in Java 10 work?
The var keyword in Java 10 is a type inference feature, which allows the compiler to automatically infer the correct type of a variable based on its initializer. This enables developers to use variables without explicitly declaring their types, leading to less verbose code and more readable syntax.
When using the var keyword, the compiler will look at what type of data is being initialized in the statement and infer the correct type accordingly. For example, if you write 'var number = 5', it will be inferred as an Integer. Similarly 'var name = "John"' would be inferred as a String. If a generic type argument is used it will also be inferred, such as with List which would become inferred as List.
The var keyword also simplifies the process of declaring generic types, allowing for shorter and simpler type declarations. For example, in Java 8, you would have to declare a List as follows: List names = new ArrayList(); With the var keyword, this same declaration can be written as var names = new ArrayList(); This makes it easier to read and understand code, as well as reducing the amount of typing required.
Furthermore, the use of the var keyword is restricted to local variables so that it does not interfere with existing code. This ensures that existing code will continue to work properly without any problems. Additionally, the compiler will throw an error if it cannot infer the correct type for a specific variable.
77. Describe the enhancements brought by the Java module system.
The Java module system, also known as Project Jigsaw, is a major enhancement to the Java platform that was introduced with the release of Java 9 in 2017. It provides an improved approach to organizing and managing large-scale applications by allowing developers to divide their application into multiple modules, each of which encapsulates its own set of classes and packages. This helps make applications more maintainable, secure, and scalable.
The main advantages of the module system are:
- 1Improved performance: Modules help reduce classpaths so that only the necessary classes are linked at runtime, leading to faster startup times for applications.
- 2Improved security: The module system enables developers to control which components can access sensitive data or libraries in their applications. This helps protect against malicious code and vulnerability exploitation.
- 3Improved maintainability: Modules provide a logical structure for organizing applications, making them easier to maintain and debug.
- 4Better support for library sharing: By allowing developers to encapsulate code into modules, the module system makes it easier to share libraries between different applications or components without risking conflicts between dependencies.
78. What are default and static methods in interfaces?
Default and static methods in interfaces are special methods introduced in Java 8 that allow for more flexibility and reusability of code within interface definitions. Default methods allow an interface to define one or more default implementations of its methods, while static methods are shared by all instances of the interface.
Default methods provide a mechanism for adding new functionality to existing interfaces without breaking any existing implementations that use them, making it easier to create backwards compatible libraries and frameworks. They also provide a way for multiple unrelated interfaces to share a single implementation, which can help reduce code duplication.
Static methods in interfaces are like static methods in classes, but they are restricted to the scope of the interface they are defined in. They can be used as utility functions or to provide a shared implementation for methods across different interfaces.
79. How does the Optional class help in handling null values?
The Optional class in Java is a container object which may or may not contain a non-null value. It provides an alternative to null checks and allows developers to express the intention of returning a value if present, rather than returning null. The Optional class helps in handling null values by providing methods to check whether a value is present or not, as well as additional methods that allow the caller to perform different actions depending on the presence of a value.
For example, there are methods such as ifPresent() that allow the caller to run certain code if the optional contains a non-null value; and orElse() which allows you to specify what should be returned if the optional does not contain a non-null value. This makes it easier for developers to handle null values gracefully and safely.
80. Describe pattern matching and its advantages in Java.
Pattern matching is a feature that was introduced in Java 14 as part of the switch expression. It allows developers to match a given input against various patterns and execute different operations depending on which pattern is matched. Pattern matching provides an alternative to traditional switch statements, allowing for more concise and expressive code.
The main advantage of using pattern matching is that it enables more precise control over the logic of a program. For instance, when using traditional switch statements, developers must check each case one by one until the correct one is found. This can be inefficient and difficult to read. With pattern matching, developers can match multiple patterns at once and easily identify which pattern has been matched without having to read through every line of code. Additionally, it can also reduce the amount of typing required, as there is no need to write out every case in a switch statement.
Java Environment & Tools
Java Environment & Tools are essential for the development and deployment of Java applications. These tools make it possible to write, compile, debug and deploy Java code.
The Java Development Kit (JDK) is a core part of the Java Environment & Tools and includes the Java Runtime Environment (JRE), compilers, debuggers, libraries, and other software used for developing Java programs and applications. The JDK also includes the Java Virtual Machine (JVM), which interprets compiled Java bytecode into machine language that can be executed by your computer's processor. The JDK can be installed on most operating systems including Windows, macOS, Linux, Solaris, AIX, and z/OS.
Other popular tools used in the Java Environment & Tools include the Eclipse Integrated Development Environment (IDE), Apache Ant for automating build processes, Maven for managing dependencies, and Git for version control. By using these tools developers can create robust and performant Java applications.
81. Differentiate between JDK, JRE, and JVM.
JDK (Java Development Kit) is a bundle of software that is used by developers to develop Java applications and applets. It includes the JRE (Java Runtime Environment), compilers, an interpreter/loader, a debugger, documentation generator, and other tools necessary for developing Java applications.
JRE (Java Runtime Environment) is a set of software tools that allow Java applications to run on any platform. It includes the Java Virtual Machine (JVM), core class libraries, and supporting files. It does not contain any development tools such as compilers or debuggers.
JVM (Java Virtual Machine) is an abstract computing machine that enables a computer to run a Java program. It interprets the bytecode into the machine code. It is responsible for allocating memory, loading class files, and executing methods.
82. How do you set the Java classpath, and why is it significant?
The Java classpath is an environment variable that tells the Java Virtual Machine (JVM) where to look for class files that are used in a Java application. It is important because it allows you to specify which directories or JAR files should be searched for classes and resources used by the application. By setting the classpath, you can make sure that your application is able to find all the necessary dependencies it needs to run correctly. Additionally, if multiple versions of a particular library are available, the classpath can be used to specify which version should be used.
83. Describe the Just-In-Time (JIT) compiler and its role in the JVM.
Just-In-Time (JIT) compiler is a crucial component of the Java Virtual Machine (JVM). It is responsible for optimizing the performance of Java applications by translating the bytecode instructions into native machine code. This translation process occurs at runtime and involves compiling the bytecode instructions to native machine language. The JIT compiler significantly increases the performance of Java programs by improving their execution speed.
The main role of the JIT compiler is to improve application performance, as it compiles bytecode instructions in real time. This helps eliminate bottlenecks that can occur when running large applications with complex logic or long loops, since code that was previously interpreted is now compiled and optimised for faster execution.
Since the JIT compiler is integrated into the JVM, it reduces the need to pre-compile all of an application’s code before runtime. This allows for quicker application start times, as only the code that is actually needed is compiled at runtime.
84. How does the javap tool help Java developers?
The javap command line tool is used by Java developers to decompile and inspect the internal structure of a compiled class file. It is an invaluable tool for debugging and understanding the inner workings of .class files, as it prints out information such as the class name, superclass, fields, methods, and bytecode instructions.
The javap tool can also be used to detect any potential problems with classes or code, such as missing classes or incorrect method signatures.
Since javap displays the names and types of all fields in a class, it can help developers create more robust code by avoiding common mistakes like type mismatches when referencing fields.
85. What is the role of the jstack tool?
The jstack tool is a utility included in the Java Development Kit (JDK), which allows developers to take a snapshot of the thread stack of a running Java application. This can be useful for troubleshooting or analyzing performance issues. It can be used to detect deadlocks, view thread information, and monitor memory usage.
The jstack command prints out a list of all threads running in the target JVM and a stack trace for each thread to show how they are organized and what methods they are currently executing. It is important to note that it does not interfere with the application's execution, but instead takes a snapshot of its current state.
86. Describe the Maven build and dependency management tool.
Maven is a build automation tool and dependency management tool primarily used for Java projects. It is designed to simplify the process of building, managing, and deploying complex projects. Maven defines a standard project structure, manages dependencies between projects, and provides automated build processes.
At its core, Maven uses a declarative model that describes how a project should be built. This model allows developers to create an executable version of the project by executing a single command on the command line, such as “mvn package” or “mvn install”. This command will read through the project's POM (Project Object Model) file which contains all the necessary information about the project like dependencies and plugins needed to build the project.
Maven also provides a powerful dependency management system that ensures all the necessary dependencies are downloaded and included in the final build. This eliminates the need for developers to manually download and include libraries, which can be time-consuming and error-prone.
87. What is bytecode in the context of Java?
Bytecode is a set of instructions that are compiled from source code written in programming languages such as Java. It is the intermediary language between the source code and machine code, which can be executed by the Java Virtual Machine (JVM). Bytecode is also referred to as “machine code” or “assembly language”, though it differs from these because it is not executed directly by hardware, but rather interpreted by the JVM.
The JVM compiles bytecode into machine code on-the-fly when programs are run. This allows for platform independent execution of Java programs, since they can be compiled once and run on any operating system with a JVM installed. Bytecode does not have to be recomp iled each time the program is run, which also helps improve performance.
88. How can you monitor and profile the performance of a Java application?
There are a variety of ways to monitor and profile the performance of a Java application.
The most common approach is to use a profiler, such as JProfiler or YourKit. Profilers allow developers to see how code is performing in real-time, giving them insight into potential bottlenecks or performance issues that might need addressing. Profilers can be used to detect memory leaks, track object creation/garbage collection, analyze thread execution and more.
Other methods for monitoring and profiling include using a debugger like Eclipse Debugger or VisualVM. These tools allow developers to step through code line by line, viewing variable values and making changes as they go. This can help identify where an issue may be occurring within the code.
Developers can also use logging to track performance. Logging allows developers to write messages to a log file that document how the application is running and can help identify issues or areas for improvement.
89. Describe the significance of the javadoc tool.
The JavaDoc tool is an important part of the Java programming language. It is used for generating API documentation in HTML format from Java source code. The Javadoc tool uses special comments in the source code to generate the output, and it can be run from a command-line or integrated into an IDE such as Eclipse.
The Javadoc tool makes it easier for developers to quickly learn about a particular API and how to use its features. This helps reduce development time by eliminating the need to manually search online documentation or other sources. Additionally, Javadoc also makes it easier for other developers to understand and debug code written by others, since they have access to useful information about classes and methods within the source code.
The Java compiler is a crucial part of the Java development process. It is responsible for transforming source code written in Java into an executable format, called bytecode, which can then be executed by the Java Virtual Machine (JVM). This process helps ensure that programs are valid and free from errors before being run on a machine. The compiler also performs optimization to help improve the performance of the code. It can detect compile-time errors such as incorrect syntax or type mismatches to help developers quickly identify and fix any issues.
90. How do tools like JUnit facilitate testing in Java applications?
JUnit is a testing framework for Java that allows developers to automate and simplify the testing process for their applications. JUnit can be used to create automated unit tests for individual classes, components, or even entire applications. The framework provides an annotation-based API and assertions which make it easy to write test cases and execute them quickly. Tests can also be organized into suites in order to provide further structure and organization.
By using JUnit, developers are able to perform quick regression tests on their code after making changes as well as validate all the components of an application before release. This makes it easier to identify any bugs or errors that may have been introduced with a change, thus reducing the amount of time spent troubleshooting and debugging.
JUnit also provides useful metrics such as code coverage which can be used to measure the quality of an application's tests.
If you are looking for more interview questions and answers, please check out these:
- 1Hibernate Interview Questions and Answers
- 2Spring Interview Questions and Answers
- 3Spring Boot Interview Questions and Answers
- 4Angular Interview Questions and Answers
- 5React Interview Questions and Answers
- 6Manual Tester Interview Questions and Answers
- 7IT Project Manager Interview Questions and Answers