0% found this document useful (0 votes)
33 views32 pages

Java Basics: Features, JVM, OOP Principles

The document provides a comprehensive overview of Java programming, covering core concepts such as features of Java, differences between JDK, JRE, and JVM, and the architecture of the Java Virtual Machine. It explains fundamental Object-Oriented Programming principles including inheritance, polymorphism, abstraction, and encapsulation, as well as the distinctions between classes and objects, method overloading, and overriding. Additionally, it outlines access modifiers and the final keyword, highlighting their roles in Java development.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views32 pages

Java Basics: Features, JVM, OOP Principles

The document provides a comprehensive overview of Java programming, covering core concepts such as features of Java, differences between JDK, JRE, and JVM, and the architecture of the Java Virtual Machine. It explains fundamental Object-Oriented Programming principles including inheritance, polymorphism, abstraction, and encapsulation, as well as the distinctions between classes and objects, method overloading, and overriding. Additionally, it outlines access modifiers and the final keyword, highlighting their roles in Java development.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Of course! Here are detailed answers to your questions on Java.

Core Java Basics


1. What are the features of Java?
Java is a popular programming language known for its distinct features:
●​ Simple: Java has a clean syntax that is easy to learn, largely based on C++.
●​ Object-Oriented: Everything in Java is an object, which helps in building modular and
reusable code.
●​ Platform Independent: Java follows the "Write Once, Run Anywhere" (WORA) principle.
Java code is compiled into bytecode (.class file), which can run on any platform that has a
Java Virtual Machine (JVM).
●​ Secure: Java provides a secure environment with features like a Security Manager that
defines access policies for applications.
●​ Robust: It's strong in terms of memory management, with automatic garbage collection
and robust exception handling.
●​ Multithreaded: Java has built-in support for multithreading, allowing for the concurrent
execution of different parts of a program.
●​ High Performance: With the Just-In-Time (JIT) compiler, Java can achieve high
performance by converting bytecode into native machine code at runtime.
●​ Distributed: It's designed for the distributed environment of the internet, with support for
network programming.

2. What is the difference between JDK, JRE, and JVM?


These are the three core components of the Java platform.
●​ JVM (Java Virtual Machine): This is an abstract machine that provides the runtime
environment in which Java bytecode can be executed. It's responsible for loading,
verifying, and executing the code. JVMs are platform-specific (e.g., you need a different
JVM for Windows, macOS, and Linux).
●​ JRE (Java Runtime Environment): This is the set of software tools for developing Java
applications. It physically exists and contains the JVM, supporting libraries, and other
components required to run Java applications. You need JRE to run a Java program.
●​ JDK (Java Development Kit): This is the full-featured SDK for Java. It contains
everything in the JRE, plus development tools like a compiler (javac), debugger (jdb), and
archiver (jar). You need JDK to develop Java applications.
In short, the relationship is: JDK = JRE + Development Tools, and JRE = JVM + Library
Classes.

3. What is the Java Virtual Machine (JVM)? Explain its architecture.


The JVM is the core of the Java platform. It's a specification that provides a runtime
environment. Its main job is to load, verify, execute code, and provide a runtime environment.
The JVM architecture consists of three main subsystems:
1.​ Classloader Subsystem: Responsible for loading, linking, and initializing class files.
○​ Loading: The Bootstrap, Extension, and Application Classloaders load classes.
○​ Linking: Performs verification (checks bytecode correctness), preparation
(allocates memory for static variables), and resolution (replaces symbolic
references with direct references).
○​ Initialization: The static initializers and static blocks of the class are executed.
2.​ Runtime Data Areas: These are memory areas used by the JVM during program
execution.
○​ Method Area: Stores class-level data like metadata, the constant pool, and static
variables.
○​ Heap Area: The primary storage for all objects created during runtime. It's a shared
memory area among all threads.
○​ Stack Area: Each thread has its own JVM stack, which stores frames. A new frame
is created for each method invocation and destroyed when the method completes.
It holds local variables and partial results.
○​ PC Registers: Each thread has its own Program Counter (PC) register, which
holds the address of the JVM instruction currently being executed.
○​ Native Method Stacks: Contains information about native (non-Java) methods.
3.​ Execution Engine: This component executes the bytecode.
○​ Interpreter: Reads, interprets, and executes bytecode instructions one by one.
○​ JIT (Just-In-Time) Compiler: Improves performance by compiling parts of the
bytecode that are frequently executed into native machine code at runtime.
○​ Garbage Collector (GC): A background thread that automatically reclaims memory
by destroying unreferenced objects.

4. What is the difference between == and .equals() in Java?


This is a common source of confusion for beginners.
●​ == operator: This is a reference comparison operator. It checks if two references point
to the exact same object in memory. For primitive types (like int, char), it compares
their actual values.
●​ .equals() method: This is a method for content comparison. It evaluates whether two
objects are logically equal by comparing their contents. The default implementation in
the Object class behaves like ==, but classes like String, Integer, etc., override it to
provide meaningful content comparison.
<!-- end list -->
String s1 = new String("HELLO");​
String s2 = new String("HELLO");​
String s3 = s1;​

[Link](s1 == s2); // false (different objects in memory)​
[Link]([Link](s2)); // true (content is the same)​
[Link](s1 == s3); // true (both refer to the same object)​

5. What are primitive and reference data types in Java?


Java has two categories of data types:
●​ Primitive Data Types: These are the most basic data types and are not objects. They
store the actual value directly in the memory where the variable is located (stack). There
are eight primitive types:
○​ byte, short, int, long (for integers)
○​ float, double (for floating-point numbers)
○​ char (for single characters)
○​ boolean (for true/false values)
●​ Reference (or Non-Primitive) Data Types: These variables do not store the object's
actual data but instead store a reference (memory address) to where the object is
located (in the heap). Examples include Classes, Interfaces, Arrays, and Strings. When
you assign one reference variable to another, you are just copying the reference, not the
object itself.

6. Explain autoboxing and unboxing


Autoboxing and unboxing are features that allow for automatic conversion between primitive
types and their corresponding wrapper classes.
●​ Autoboxing: The automatic conversion that the Java compiler makes from a primitive
type to its corresponding object wrapper class. For example, converting an int to an
Integer.
●​ Unboxing: The reverse of autoboxing. It's the automatic conversion of a wrapper class
object to its corresponding primitive type. For example, converting an Integer to an int.
<!-- end list -->
// Autoboxing: int to Integer​
Integer i = 100;​

// Unboxing: Integer to int​
int j = i;​

[Link](j); // 100​

Object-Oriented Programming (OOP)


7. What are the four main principles of OOP?
The four core principles of Object-Oriented Programming are:
1.​ Inheritance: The mechanism by which one class (the child or subclass) acquires the
properties (fields) and behaviors (methods) of another class (the parent or superclass). It
promotes code reuse.
2.​ Polymorphism: The ability of an object to take on many forms. It allows a single action to
be performed in different ways. In Java, it's achieved through method overloading and
method overriding.
3.​ Abstraction: Hiding complex implementation details and showing only the essential
features of the object. It focuses on what an object does rather than how it does it. It's
achieved using abstract classes and interfaces.
4.​ Encapsulation: The bundling of data (attributes) and the methods that operate on the
data into a single unit (a class). It also involves restricting direct access to some of an
object's components, which is known as data hiding.
8. What is inheritance? Types of inheritance in Java?
Inheritance is a fundamental OOP concept where a new class derives properties and methods
from an existing class. The class that inherits is the subclass (or child class), and the class
being inherited from is the superclass (or parent class). The extends keyword is used to
achieve inheritance.
Java supports the following types of inheritance:
●​ Single Inheritance: A class inherits from only one superclass. (Class B extends A)
●​ Multilevel Inheritance: A class inherits from a class, which in turn inherits from another
class. (Class C extends B, and Class B extends A)
●​ Hierarchical Inheritance: Multiple classes inherit from a single superclass. (Class B
extends A, and Class C extends A)
Multiple Inheritance (a class inheriting from more than one superclass) is not supported in
Java through classes to avoid the "Diamond Problem" (ambiguity when two parent classes have
a method with the same signature). However, it can be achieved through interfaces, as a class
can implement multiple interfaces.

9. What is polymorphism? Difference between compile-time and


runtime polymorphism.
Polymorphism, meaning "many forms," allows us to perform a single action in different ways.
●​ Compile-time Polymorphism (Static Binding): This is achieved through method
overloading. The call to an overloaded method is resolved at compile time based on the
method signature (number, type, and order of parameters). It's also called static
polymorphism.
●​ Runtime Polymorphism (Dynamic Binding): This is achieved through method
overriding. An overridden method is called through the reference variable of a
superclass. The JVM determines which method to call at runtime based on the actual
object type, not the reference type. It's also called dynamic polymorphism.
<!-- end list -->
// Compile-time Polymorphism (Overloading)​
class Calculator {​
int add(int a, int b) { return a + b; }​
double add(double a, double b) { return a + b; }​
}​

// Runtime Polymorphism (Overriding)​
class Animal {​
void sound() { [Link]("Animal makes a sound"); }​
}​
class Dog extends Animal {​
@Override​
void sound() { [Link]("Dog barks"); }​
}​

// Usage​
Animal myDog = new Dog(); // Reference is Animal, object is Dog​
[Link](); // Output: "Dog barks" (Resolved at runtime)​

10. What is abstraction? How is it achieved in Java?


Abstraction is the concept of hiding the internal implementation details and showing only the
functionality to the user. For example, when you send an SMS, you just type the message and
send it; you don't know the internal processing.
In Java, abstraction is achieved in two ways:
1.​ Abstract Classes: A class declared with the abstract keyword. It can have abstract
(methods without a body) and non-abstract methods. It cannot be instantiated. A class
must extend it and implement its abstract methods.
2.​ Interfaces: An interface is a blueprint of a class. It contains only abstract methods (before
Java 8) and static final variables. A class implements an interface and must provide the
implementation for all its methods.

11. What is encapsulation and how does Java achieve it?


Encapsulation is the practice of wrapping data (variables) and code acting on the data
(methods) together as a single unit. It's a protective shield that prevents the data from being
accessed by the code outside this shield.
Java achieves encapsulation by:
1.​ Declaring the variables of a class as private.
2.​ Providing public setter and getter methods to modify and view the variable values.
This prevents direct, uncontrolled access to the data fields and ensures that the data can only
be modified through controlled methods, which can include validation logic.
public class Employee {​
private String name; // Data is private​

// Public getter​
public String getName() {​
return name;​
}​

// Public setter with validation​
public void setName(String name) {​
if (name != null && ![Link]()) {​
[Link] = name;​
}​
}​
}​

Classes and Objects


12. Difference between class and object.
●​ Class: A class is a blueprint or template from which objects are created. It defines the
properties (attributes) and behaviors (methods) that its objects will have. It's a logical
entity and doesn't occupy memory until an object is created.
●​ Object: An object is an instance of a class. It's a real-world entity that has state (values
of its attributes) and behavior (its methods). When an object is created using the new
keyword, memory is allocated for it in the heap.
Analogy: If a Car is a class (blueprint), then a specific Ford Mustang or a Toyota Camry are
objects (instances) of that class.

13. What is constructor overloading?


Constructor overloading is the practice of having multiple constructors within the same class,
each with a different parameter list (different number of parameters, different types of
parameters, or both). The compiler decides which constructor to invoke based on the arguments
passed when creating an object.
class Box {​
double width, height, depth;​

// Constructor with no parameters​
Box() {​
width = height = depth = 0;​
}​

// Constructor with one parameter​
Box(double len) {​
width = height = depth = len;​
}​

// Constructor with three parameters​
Box(double w, double h, double d) {​
width = w;​
height = h;​
depth = d;​
}​
}​

14. What is the use of the this keyword?


The this keyword in Java is a reference variable that refers to the current object. It has several
uses:
1.​ To refer to the current class instance variable: It's used to differentiate between
instance variables and local variables/parameters if they have the same name.
2.​ To invoke the current class constructor: this() can be used to call another constructor
from within a constructor of the same class (constructor chaining).
3.​ To invoke the current class method: [Link]() can be used to call a method
of the current class.
4.​ To be passed as an argument in a method call.
5.​ To be passed as an argument in a constructor call.
6.​ To return the current class instance from a method.
<!-- end list -->
public class Student {​
private String name;​

public Student(String name) {​
// Use 'this' to distinguish instance variable from parameter​
[Link] = name;​
}​
}​

15. Can we override static methods?


No, we cannot override static methods in Java. Overriding is based on dynamic binding at
runtime, but static methods are bonded at compile time using static binding.
If a subclass defines a static method with the same signature as a static method in the
superclass, it's known as method hiding, not overriding. The method that gets called depends
on the type of the reference, not the type of the underlying object.

16. What is method overloading and method overriding?


●​ Method Overloading: This occurs when two or more methods in the same class have
the same name but different parameters (different number, type, or order of parameters).
It's a way to achieve compile-time polymorphism. The return type can be different, but the
parameters must be different.
●​ Method Overriding: This occurs when a subclass has a method with the same name,
parameters, and return type as a method in its superclass. It's a way to achieve runtime
polymorphism. The subclass provides a specific implementation of a method that is
already defined in its parent class. The @Override annotation should be used for clarity
and compile-time checking.
Feature Method Overloading Method Overriding
Purpose Increase program readability Provide specific implementation
in subclass
Location Within the same class In two classes (superclass and
subclass)
Parameters Must be different Must be the same
Polymorphism Compile-time (Static) Run-time (Dynamic)
Binding Static Binding Dynamic Binding
Access Modifiers and Keywords
17. What are the different access modifiers in Java?
Access modifiers in Java specify the accessibility or scope of a field, method, constructor, or
class. There are four types:
1.​ public: The member is accessible from everywhere—from within the class, from other
classes within the same package, and from classes outside the package.
2.​ protected: The member is accessible within its own package and by subclasses in other
packages.
3.​ default (no keyword): The member is accessible only within its own package. It is also
called package-private.
4.​ private: The member is accessible only within its own class.

18. What is the final keyword? (variable, method, class)


The final keyword is used to restrict the user. It can be applied to variables, methods, and
classes.
●​ final variable: When a variable is declared as final, its value cannot be changed once it
has been assigned. It essentially becomes a constant.
●​ final method: When a method is declared as final, it cannot be overridden by any
subclass.
●​ final class: When a class is declared as final, it cannot be extended or inherited by any
other class. For example, the String class is final.

19. Difference between abstract class and interface.


Both are used to achieve abstraction but have key differences:
Feature Abstract Class Interface
Methods Can have both abstract and Can only have abstract
non-abstract (concrete) methods (before Java 8). Since
methods. Java 8, can have default and
static methods with
implementation.
Variables Can have final, non-final, static, Variables are public static final
and non-static variables. by default.
Inheritance A class can extend only one A class can implement multiple
abstract class. interfaces.
Keyword Use the abstract keyword to Use the interface keyword to
declare, and extends to inherit. declare, and implements to
use.
Constructor Can have a constructor (called Cannot have a constructor.
when a subclass is
instantiated).
Purpose To provide a base for To define a contract that
subclasses with some common implementing classes must
implementation. adhere to.
20. What is the static keyword used for?
The static keyword in Java is used for memory management. A static member belongs to the
class itself rather than to an instance of the class.
●​ static variable: Also known as a class variable. There is only one copy of this variable,
shared among all objects of the class. It's allocated memory only once when the class is
loaded.
●​ static method: Also known as a class method. It can be called without creating an object
of the class ([Link]()). It can only access static data members and can
only call other static methods directly. It cannot use this or super.
●​ static block: Used to initialize static data members. It is executed only once, when the
class is loaded into memory.
●​ static nested class: A class created within a class. It can only access static members of
the outer class.

21. What is the super keyword?


The super keyword is a reference variable that is used to refer to the immediate parent class
object. Its uses are:
1.​ super can be used to refer to an immediate parent class's instance variable. This is
useful if the subclass and superclass have members with the same name.
2.​ super can be used to invoke an immediate parent class's method. This is useful if the
subclass has overridden a method and wants to call the parent's version.
3.​ super() can be used to invoke an immediate parent class's constructor. This must be the
first statement in the subclass constructor.
<!-- end list -->
class Animal {​
String color = "white";​
void eat() { [Link]("eating..."); }​
}​
class Dog extends Animal {​
String color = "black";​
void printColor() {​
[Link](color); // Prints color of Dog class​
[Link]([Link]); // Prints color of Animal
class​
}​
void eat() {​
[Link](); // Calls parent's eat method​
[Link]("eating bread...");​
}​
}​

String Handling
22. What is the difference between String, StringBuilder, and
StringBuffer?
This is a classic Java interview question focusing on string mutability and thread safety.
Feature String StringBuffer StringBuilder
Mutability Immutable. Once Mutable. Can be Mutable. Same as
created, its value modified after creation StringBuffer.
cannot be changed. A without creating a new
new object is created object.
for every modification.
Thread Safety Thread-safe because Thread-safe. Methods Not thread-safe.
it's immutable. are synchronized. Methods are not
synchronized.
Performance Slow for frequent Slower than Fastest for
modifications due to StringBuilder because modifications as it's not
new object creation. of synchronization synchronized.
overhead.
When to use When the string value In a multithreaded In a single-threaded
will not change. environment where environment where
string modification is string modification is
needed. needed.
23. Why is String immutable in Java?
There are several key reasons for the immutability of String objects in Java:
1.​ String Pool: Java uses a special memory region called the String Pool. If a string is
immutable, the JVM can optimize by having multiple string references point to the same
string object in the pool, saving memory. If strings were mutable, changing the string for
one reference would affect all other references, which would be undesirable.
2.​ Security: String parameters are widely used in network connections, database URLs,
usernames/passwords, etc. If strings were mutable, these values could be changed after
a security check, posing a huge security risk.
3.​ Synchronization and Thread Safety: Because strings are immutable, they are
inherently thread-safe. They can be shared among multiple threads without any need for
synchronization.
4.​ Caching: The hashcode of a string is frequently used in collections like HashMap and
HashSet. Since the string is immutable, its hashcode is cached at creation time. This
makes it a very fast key for hash-based collections because the hashcode doesn't need to
be re-calculated every time.

24. What is the string pool in Java?


The String Pool (also known as the String Constant Pool or String Intern Pool) is a special
storage area in the Java heap. When we create a string literal (e.g., String s = "Java";), the JVM
first checks if a string with the same value already exists in the pool.
●​ If it exists, the reference of the existing string is returned.
●​ If it doesn't exist, a new string object is created in the pool, and its reference is returned.
This mechanism saves memory by avoiding the creation of duplicate string objects. Strings
created with the new keyword (e.g., String s = new String("Java");) are always created in the
heap outside the pool, even if a string with the same value already exists in the pool. The
intern() method can be used to manually place a string from the heap into the pool.
25. How to compare two strings in Java?
There are two primary ways to compare strings in Java:
1.​ Using the .equals() method: This is the most common and recommended way. It
compares the actual content of the strings. It returns true if the character sequences are
identical and false otherwise. It is case-sensitive.
○​ [Link](s2)
○​ [Link](s2): Compares content ignoring case differences.
2.​ Using the == operator: This operator compares the references (memory locations) of
the two string objects, not their content. It returns true only if both references point to the
same object in memory. This is generally not what you want for string content comparison,
except in specific cases involving the string pool.
3.​ Using the .compareTo() method: This method compares two strings lexicographically
(like in a dictionary). It returns:
○​ 0 if the strings are equal.
○​ A value less than 0 if the string object comes before the argument string.
○​ A value greater than 0 if the string object comes after the argument string.
<!-- end list -->
String s1 = "hello";​
String s2 = new String("hello");​
String s3 = "hello";​

[Link](s2); // true (content is same)​
s1 == s2; // false (different objects)​
s1 == s3; // true (both refer to same object in string pool)​

Exception Handling
26. What is exception handling in Java?
Exception handling is a mechanism to handle runtime errors such as ClassNotFoundException,
IOException, SQLException, etc. The primary goal is to maintain the normal flow of the
application even when an error occurs. An exception disrupts the normal flow of the program.
Java uses five keywords for exception handling:
●​ try: The block of code to be monitored for exceptions.
●​ catch: The block of code that handles the exception. It's executed if an exception occurs
in the try block.
●​ finally: The block of code that is always executed, whether an exception is handled or not.
It's used for cleanup activities like closing connections.
●​ throw: Used to manually throw an exception.
●​ throws: Used in a method signature to declare the exceptions that the method might
throw.

27. Difference between checked and unchecked exceptions.


Java exceptions are categorized into two main types:
●​ Checked Exceptions: These are exceptions that are checked at compile-time. If a
method's code can throw a checked exception, the method must either handle it using a
try-catch block or declare it using the throws keyword. Examples include IOException,
SQLException, ClassNotFoundException. They are subclasses of Exception (excluding
RuntimeException).
●​ Unchecked Exceptions: These are exceptions that are not checked at compile-time.
The compiler doesn't force you to handle them. They usually arise from programming
errors, such as logic errors or improper use of an API. Examples include
NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException. They are
subclasses of RuntimeException.
There's also a third category, Errors, which are also unchecked but are reserved for serious
problems that a reasonable application should not try to catch, like OutOfMemoryError or
StackOverflowError.

28. What is the use of finally block?


The finally block is used to execute important code such as closing resources (like database
connections, files, scanners, etc.). The finally block is always executed regardless of whether
an exception occurs in the try block or not. It will even be executed if a return statement is
present in the try or catch block.
The only time a finally block will not be executed is if the program exits by calling [Link]()
or if a fatal error causes the JVM to crash.
try {​
// Risky code​
} catch (Exception e) {​
// Exception handling​
} finally {​
// Cleanup code, always executed​
}​

29. What are throw and throws?


●​ throw keyword: The throw keyword is used to explicitly throw an exception from a
method or any block of code. It is used to throw a single exception, either a new instance
or a pre-existing one. It's followed by an instance of a Throwable class.
●​ throws keyword: The throws keyword is used in a method signature to declare the
types of exceptions that might be thrown by that method. It informs the caller of the
method about the exceptions it needs to handle. The caller must either handle these
exceptions using try-catch or propagate them by declaring them in its own throws clause.
<!-- end list -->
// 'throw' is used inside a method​
void validateAge(int age) {​
if (age < 18) {​
throw new ArithmeticException("Not eligible to vote");​
}​
}​

// 'throws' is used in the method signature​
void readFile() throws IOException {​
// Code that might throw an IOException​
}​

30. Can we have multiple catch blocks?


Yes, a try block can be followed by multiple catch blocks. This is useful for handling different
types of exceptions in different ways.
When an exception is thrown in the try block, the catch blocks are evaluated in order from top to
bottom. The first catch block whose exception type matches the type of the thrown exception (or
is a superclass of it) is executed. After that, no other catch blocks are checked. Therefore, it's
important to order the catch blocks from the most specific exception type to the most general.
Placing the more general exception (catch (Exception e)) before a more specific one (catch
(IOException e)) will result in a compile-time error.
try {​
// some code​
} catch (ArithmeticException e) {​
// handle arithmetic exception​
} catch (ArrayIndexOutOfBoundsException e) {​
// handle array index out of bounds exception​
} catch (Exception e) {​
// handle all other exceptions​
}​

Collections Framework
31. What is the Java Collections Framework?
The Java Collections Framework is a unified architecture for representing and manipulating
collections (groups of objects). It provides a set of interfaces and classes to manage collections
efficiently.
Key components are:
●​ Interfaces: Abstract data types that represent collections (Collection, List, Set, Map,
Queue, etc.).
●​ Implementations (Classes): Concrete implementations of the collection interfaces
(ArrayList, LinkedList, HashSet, HashMap, etc.).
●​ Algorithms: Methods that perform useful computations on objects that implement
collection interfaces, such as searching and sorting (provided by the Collections utility
class).

32. Difference between List, Set, and Map.


These are three main interfaces in the Collections Framework.
●​ List: An ordered collection (also known as a sequence).
○​ Allows duplicate elements.
○​ Maintains insertion order.
○​ Elements can be accessed by their integer index (position).
○​ Implementations: ArrayList, LinkedList, Vector.
●​ Set: A collection that contains no duplicate elements.
○​ Does not allow duplicate elements.
○​ Does not guarantee insertion order (except for LinkedHashSet).
○​ No index-based access.
○​ Implementations: HashSet, LinkedHashSet, TreeSet.
●​ Map: An object that maps keys to values. It's not a true collection as it doesn't extend the
Collection interface.
○​ Contains values on the basis of a key.
○​ Each key must be unique; it cannot contain duplicate keys.
○​ It can have duplicate values.
○​ Implementations: HashMap, LinkedHashMap, TreeMap, Hashtable.

33. What is the difference between ArrayList and LinkedList?


Both ArrayList and LinkedList are implementations of the List interface, but they have different
underlying data structures.
Feature ArrayList LinkedList
Underlying Structure Dynamic Array. Doubly Linked List.
Manipulation Slow. Adding/removing Fast. Adding/removing
elements from the middle is elements is fast because it only
slow because it requires involves updating the links of
shifting subsequent elements. the adjacent nodes.
Access/Search Fast. It provides random Slow. To access an element,
access, so getting an element we have to traverse the list
at a specific index (get(index)) from the beginning or end,
is very fast, taking O(1) time. taking O(n) time.
Memory Overhead Lower memory overhead as it Higher memory overhead as
only stores the data. each node stores the data plus
the addresses of the previous
and next nodes.
When to use When the main operation is When the main operation is
searching or retrieving adding or deleting elements
elements. from the middle of the list.
34. Difference between HashSet and TreeSet.
Both HashSet and TreeSet implement the Set interface and do not allow duplicates.
Feature HashSet TreeSet
Underlying Structure Uses a HashMap internally for Uses a TreeMap (a Red-Black
storage. Tree) internally for storage.
Ordering Unordered. It does not Ordered. Elements are sorted
guarantee any order of in their natural ascending order
elements. (or by a provided Comparator).
Performance Faster for insertion, deletion, Slower than HashSet, with
Feature HashSet TreeSet
and retrieval operations, performance of O(\\log n) for
typically O(1) on average. most operations, because it has
to maintain the sorted order.
Null Elements Allows one null element. Does not allow null elements
(throws NullPointerException)
because it needs to compare
elements to maintain order.
Implementation Uses the hashCode() and Uses the compareTo() method
equals() methods for storing (or compare() from a
and comparing objects. Comparator) for ordering
elements.
35. Difference between HashMap and Hashtable.
HashMap and Hashtable both store key-value pairs and use hashing for storage. HashMap is
generally preferred over Hashtable.
Feature HashMap Hashtable
Synchronization Not synchronized. It is not Synchronized. It is
thread-safe. thread-safe. All its methods are
synchronized.
Null Keys/Values Allows one null key and Does not allow any null key or
multiple null values. null value (throws
NullPointerException).
Performance Faster because it's Slower due to the overhead of
non-synchronized. synchronization.
Iterator Uses Iterator, which is fail-fast. Uses Enumerator and Iterator.
Enumerator is not fail-fast.
Inheritance Extends AbstractMap. Extends Dictionary (which is
now obsolete).
Introduced Part of the Collections A legacy class from older Java
Framework since Java 1.2. versions.
For a thread-safe alternative to HashMap, ConcurrentHashMap is a much better choice than
Hashtable.

36. What is ConcurrentHashMap?


ConcurrentHashMap is a class introduced in Java 1.5. It's a thread-safe and highly scalable
implementation of the Map interface. It provides a concurrent alternative to Hashtable with much
better performance.
Instead of locking the entire map for every modification (like Hashtable), ConcurrentHashMap
uses a technique called lock striping or segmented locking. The map is divided into multiple
segments (or bins in Java 8+), and each segment is governed by its own lock. When a thread
wants to modify the map, it only locks the specific segment it's working on, allowing other
threads to access other segments concurrently. This significantly improves concurrency and
performance in multithreaded applications.
Key features:
●​ Thread-safe without synchronizing the whole map.
●​ Reads can happen concurrently with writes.
●​ Allows multiple null values but no null keys.
●​ Its iterator does not throw ConcurrentModificationException.

37. How does HashMap work internally?


HashMap works on the principle of hashing. It stores data in key-value pairs in an array of
nodes (or buckets).
1.​ put(key, value):
○​ When you call put(key, value), HashMap first calculates the hashCode() of the key.
○​ This hash code is then passed through an internal hash function to determine the
index of the bucket in the underlying array where the entry should be stored.
○​ If the bucket is empty, a new Node(hash, key, value, null) is created and placed in
that bucket.
○​ If the bucket is not empty (a hash collision), HashMap iterates through the linked
list (or balanced tree in Java 8+ for large lists) at that bucket index.
○​ It compares the new key with the existing keys using the equals() method.
○​ If a key with the same hashCode and equals value is found, the old value is
replaced with the new value.
○​ If no matching key is found, the new key-value pair is added to the end of the linked
list (or tree).
2.​ get(key):
○​ To retrieve a value, it again calculates the hashCode of the key and finds the bucket
index.
○​ It then iterates through the list/tree in that bucket, using equals() to find the exact
key, and returns the corresponding value. If not found, it returns null.
Important: For a custom object to be used as a key in HashMap, it's crucial to correctly override
both the hashCode() and equals() methods.

Multithreading and Concurrency


38. What is a thread in Java?
A thread is the smallest unit of execution within a process. It is a lightweight subprocess with its
own program counter, stack, and local variables. Multiple threads can exist within a single
process and share resources like memory and files, allowing for concurrent execution and
improving application performance and responsiveness.
Java provides built-in support for multithreading. The main thread is the one that executes the
main() method of a program.

39. Difference between Runnable and Thread.


There are two ways to create a thread in Java:
1.​ Extending the Thread class:
○​ Create a class that extends [Link].
○​ Override the run() method with the code to be executed by the thread.
○​ Create an object of this class and call the start() method to begin execution.
○​ Limitation: Java does not support multiple inheritance, so if your class needs to
extend another class, you cannot also extend Thread.
2.​ Implementing the Runnable interface:
○​ Create a class that implements [Link].
○​ Implement the run() method.
○​ Create an instance of this class.
○​ Create an instance of the Thread class, passing the Runnable object to its
constructor.
○​ Call the start() method on the Thread object.
○​ Advantage: This is the preferred method. It allows for multiple inheritance of
behavior (implementing multiple interfaces) and promotes better separation of
concerns (the task to be run is separate from the thread mechanism).

40. What is the lifecycle of a thread?


A thread in Java goes through several states during its lifecycle:
1.​ New: The thread is in this state after a new Thread object is created but before the start()
method is invoked.
2.​ Runnable: The thread enters this state after the start() method is invoked. It is now
considered to be executing its task, but it might be waiting for the scheduler to allocate
CPU time. This state includes both "running" and "ready to run".
3.​ Blocked/Waiting: A thread is in this state when it is temporarily inactive. It can enter this
state for several reasons:
○​ Blocked: Waiting to acquire a monitor lock (e.g., to enter a synchronized block).
○​ Waiting: Waiting indefinitely for another thread to perform a particular action (e.g.,
after calling [Link]() or [Link]()).
○​ Timed Waiting: Waiting for a specified amount of time (e.g., after calling
[Link](time) or [Link](time)).
4.​ Terminated (Dead): A thread enters this state when it has finished its execution (its run()
method completes) or when it is abnormally terminated. Once a thread is in this state, it
cannot be started again.

41. What is synchronization? How is it used?


Synchronization is a mechanism that controls the access of multiple threads to any shared
resource. It's a way to prevent race conditions and ensure data consistency in a multithreaded
environment.
When a thread enters a synchronized method or block, it acquires a monitor lock on the object.
As long as the thread holds the lock, no other thread can enter any synchronized block/method
on the same object. Other threads will be blocked until the lock is released.
Synchronization can be achieved in two ways:
1.​ Synchronized Method: By declaring a method with the synchronized keyword.​
public synchronized void myMethod() {​
// thread-safe code​
}​

2.​ Synchronized Block: By using the synchronized keyword on a block of code. This is
more efficient as it locks only the critical section of the code, not the entire method.​
public void myMethod() {​
// non-critical code​
synchronized(this) { // or some other lock object​
// critical section - thread-safe code​
}​
// non-critical code​
}​

42. Difference between wait(), notify(), and notifyAll()


These methods, which belong to the Object class, are used for inter-thread communication.
They must be called from within a synchronized block or method.
●​ wait(): Causes the current thread to release the lock and wait until another thread invokes
the notify() or notifyAll() method for this object. The thread moves to the Waiting state.
●​ notify(): Wakes up a single thread that is waiting on this object's monitor. If multiple
threads are waiting, one is chosen arbitrarily. The awakened thread will not run
immediately; it must wait to re-acquire the object's lock.
●​ notifyAll(): Wakes up all threads that are waiting on this object's monitor. Each awakened
thread will then compete to acquire the lock.
Using notifyAll() is generally safer than notify() to avoid situations where the wrong thread is
notified, leading to deadlocks.

Lambda Expressions
43. What is a lambda expression in Java 8?
A lambda expression is a short block of code which takes in parameters and returns a value. It's
an anonymous function (a function without a name) that allows you to treat functionality as a
method argument, or code as data. Lambda expressions are a key feature introduced in Java 8
to promote functional programming.

44. What are the advantages of using lambda expressions?


●​ Concise Code: They drastically reduce the amount of code you need to write, especially
when compared to anonymous inner classes.
●​ Readability: For simple operations, they can make the intent of the code clearer.
●​ Functional Programming: They enable functional programming paradigms in Java.
●​ Higher Efficiency (with Streams): They enable support for parallel processing on
collections through the Stream API, which can improve performance.
●​ Passing Behavior: They allow you to easily pass behavior (code) as an argument to
methods.

45. Explain the syntax of a lambda expression.


A lambda expression consists of three parts:
1.​ Argument List: A list of parameters enclosed in parentheses (). You can omit the data
type of the parameters. If there is only one parameter, you can omit the parentheses.
2.​ Arrow Token: The -> symbol.
3.​ Body: A single expression or a block of code enclosed in curly braces {}. If the body is a
single expression, you can omit the curly braces and the return keyword.
<!-- end list -->
// 1. With type declaration​
(int a, int b) -> a + b;​

// 2. Without type declaration (type inference)​
(a, b) -> a + b;​

// 3. With curly braces and return statement​
(a, b) -> { return a + b; };​

// 4. Single parameter, no parentheses​
message -> [Link](message);​

// 5. No parameters​
() -> [Link]("Hello World");​

46. What are functional interfaces, and how are they related to lambda
expressions?
A functional interface is an interface that contains exactly one abstract method. The Java
compiler can infer the type of a lambda expression based on the context in which it's used. This
context must be a functional interface.
The relationship is that a lambda expression provides the implementation for the single
abstract method of a functional interface. You can think of a lambda expression as a concise
way to create an instance of a functional interface.
The @FunctionalInterface annotation can be used to mark an interface as functional. It's
optional, but it helps the compiler to verify that the interface has only one abstract method.

47. Can lambda expressions access local variables? What are the
rules?
Yes, lambda expressions can access local variables from the enclosing scope. However, there
is a strict rule: the local variable must be final or effectively final.
●​ Final: The variable is explicitly declared with the final keyword.
●​ Effectively Final: The variable is not declared as final, but its value is never changed
after it is initialized.
This restriction exists because lambda expressions can be executed in a different thread or long
after the method in which they were created has returned. To ensure thread safety and avoid
concurrency issues, the lambda must have its own "copy" of the variable, which is only possible
if the variable's value is guaranteed not to change.
Functional Interfaces
48. What is a functional interface? Give examples.
A functional interface is an interface that has only one abstract method. They are also known as
Single Abstract Method (SAM) interfaces. They form the basis for lambda expressions.
Examples:
●​ Runnable: Has the single abstract method run().
●​ ActionListener: Has the single abstract method actionPerformed().
●​ Comparator: Has the single abstract method compare().
●​ Callable: Has the single abstract method call().

49. What is the @FunctionalInterface annotation?


@FunctionalInterface is an informative annotation used to indicate that an interface is intended
to be a functional interface. It's not mandatory, but it's good practice to use it. If an interface is
annotated with @FunctionalInterface and it does not satisfy the conditions (i.e., it has more than
one abstract method, or none), the compiler will generate an error. This helps to avoid
accidentally adding new abstract methods to a functional interface.

50. List some commonly used functional interfaces in Java 8.


Java 8 introduced a new package [Link] containing many new functional interfaces.
Some of the most common ones are:

51. Predicate
●​ Predicate<T>: Represents a predicate (a boolean-valued function) of one argument.
○​ Abstract Method: boolean test(T t)
○​ Use Case: Filtering elements (e.g., [Link](...)).

52. Consumer
●​ Consumer<T>: Represents an operation that accepts a single input argument and
returns no result.
○​ Abstract Method: void accept(T t)
○​ Use Case: Performing an action on each element (e.g., [Link](...)).

53. Supplier
●​ Supplier<T>: Represents a supplier of results. It takes no arguments and returns a value.
○​ Abstract Method: T get()
○​ Use Case: Generating or providing new objects.

54. Function
●​ Function<T, R>: Represents a function that accepts one argument and produces a result.
○​ Abstract Method: R apply(T t)
○​ Use Case: Transforming an element into another (e.g., [Link](...)).

54. Can we define our own functional interface? How?


Yes, you can easily define your own functional interface. Simply create an interface with a single
abstract method and, optionally, annotate it with @FunctionalInterface.
@FunctionalInterface​
interface MathOperation {​
int operate(int a, int b);​
}​

public class Calculator {​
public static void main(String[] args) {​
// Use the custom functional interface with a lambda​
MathOperation addition = (a, b) -> a + b;​
[Link]("10 + 5 = " + [Link](10, 5));​
}​
}​

Stream API
55. What is the Stream API in Java 8?
The Stream API is a new feature in Java 8 used to process collections of objects in a functional
style. A stream is a sequence of elements that supports various aggregate operations. It's not a
data structure that stores elements; instead, it takes input from Collections, Arrays, or I/O
channels.
Key characteristics:
●​ Represents a sequence of elements.
●​ Supports functional-style operations (like filter, map, reduce).
●​ Operations can be pipelined.
●​ Supports lazy evaluation.
●​ Can be processed sequentially or in parallel.

56. What is the difference between Collection and Stream?


Feature Collection Stream
Data Storage A data structure that stores Not a data structure. It's a view
elements in memory. over a data source (like a
collection) and doesn't store
elements.
Traversal Can be iterated multiple times. Can be traversed only once.
After a terminal operation is
called, the stream is consumed.
Operations Operations are performed Operations are performed
Feature Collection Stream
eagerly. lazily. Computations start only
when a terminal operation is
invoked.
Modification Collections are mutable. We Streams are not modifiable.
can add or remove elements. Operations on a stream
produce a new stream.
Main Use Storing and managing a group Performing complex aggregate
of elements. operations (filter, map, reduce)
on data.
57. What are intermediate and terminal operations in streams?
Stream operations are divided into two categories:
1.​ Intermediate Operations: These operations transform a stream into another stream.
They are always lazy, meaning they don't get executed until a terminal operation is
invoked. They can be chained together to form a pipeline.
○​ Examples: filter(), map(), sorted(), distinct(), flatMap().
2.​ Terminal Operations: These operations produce a result or a side-effect. They trigger
the processing of the stream pipeline. After a terminal operation is performed, the stream
is consumed and cannot be used again.
○​ Examples: forEach(), collect(), reduce(), count(), anyMatch(), findFirst().
<!-- end list -->
List<String> names = [Link]("Alice", "Bob", "Charlie");​
long count = [Link]() // Create stream​
.filter(s -> [Link]() > 3) // Intermediate operation​
.map(String::toUpperCase) // Intermediate operation​
.count(); // Terminal operation​

58. What are some commonly used methods in the Stream API?
●​ filter(Predicate p): Returns a stream of elements that match the given predicate.
●​ map(Function f): Returns a stream consisting of the results of applying the given function
to the elements of this stream.
●​ flatMap(Function f): Transforms each element into a stream of other objects and then
flattens these streams into a single stream.
●​ sorted(): Sorts the stream elements in natural order.
●​ distinct(): Returns a stream with unique elements.
●​ forEach(Consumer c): Performs an action for each element of the stream (terminal).
●​ collect(Collector c): Performs a mutable reduction operation, like collecting elements into
a List or Map (terminal).
●​ reduce(): Performs a reduction on the elements of the stream, returning a single result
(terminal).
●​ count(): Returns the count of elements in the stream (terminal).
●​ anyMatch(), allMatch(), noneMatch(): Check if elements match a predicate (terminal).
●​ findFirst(), findAny(): Find an element in the stream (terminal).
59. How is map() different from flatMap()?
Both map() and flatMap() are intermediate operations that apply a function to the elements of a
stream.
●​ map(Function<T, R>): This operation transforms each element of a stream from type T to
type R. If you have a Stream<T>, applying map with a Function<T, R> gives you a
Stream<R>. It's a one-to-one transformation.
○​ Example: Stream<String> -> map(s -> [Link]()) -> Stream<Integer>
●​ flatMap(Function<T, Stream<R>>): This operation is a combination of map and a flatten
operation. It transforms each element into a stream of other objects and then flattens all
the generated streams into a single, new stream. It's a one-to-many transformation.
○​ Example: Stream<List<Integer>> -> flatMap(list -> [Link]()) -> Stream<Integer>
You use map when your transformation function returns a single value. You use flatMap when
your transformation function returns a stream of values for each input element, and you want to
combine them into a single stream.

60. What is lazy evaluation in streams?


Lazy evaluation is a key characteristic of Java streams. It means that intermediate operations in
a stream pipeline are not executed immediately. They are only executed when a terminal
operation is invoked.
This approach offers significant performance benefits:
●​ Efficiency: The stream can process elements one by one through the entire pipeline,
rather than processing the entire collection at each step. This avoids creating large
intermediate collections.
●​ Short-circuiting: Some terminal operations (like findFirst, anyMatch) might not need to
process the entire stream. For example, findFirst will stop as soon as it finds the first
element that satisfies the condition. Lazy evaluation makes this possible.

61. How to use filter(), map(), collect() with examples?


These are three of the most common stream operations.
●​ filter(Predicate<T> predicate): Selects elements based on a condition.
●​ map(Function<T, R> mapper): Transforms each element.
●​ collect(Collector collector): Gathers the results into a collection.
Example: Given a list of Product objects, find the names of all electronic products with a price
greater than 500, and return them as a list of uppercase strings.
class Product {​
String name;​
String category;​
double price;​
// constructor, getters​
}​

List<Product> products = // ... initialize list of products​

List<String> expensiveElectronicsNames = [Link]() // 1. Get
stream​
.filter(p -> "Electronics".equals([Link]())) // 2. Filter
by category​
.filter(p -> [Link]() > 500) // 3. Filter
by price​
.map(p -> [Link]().toUpperCase()) // 4. Map to
uppercase name​
.collect([Link]()); // 5.
Collect results into a List​

62. How to perform sorting using streams?


The sorted() method is used for sorting stream elements.
●​ sorted(): Sorts the elements in their natural order (the class must implement
Comparable).
●​ sorted(Comparator<T> comparator): Sorts the elements according to the provided
Comparator.
<!-- end list -->
List<String> names = [Link]("Charlie", "Alice", "Bob");​

// Natural sorting (alphabetical)​
List<String> sortedNames = [Link]()​
.sorted()​
.collect([Link]());​
// Result: ["Alice", "Bob", "Charlie"]​

// Reverse sorting​
List<String> reverseSortedNames = [Link]()​

.sorted([Link]())​
.collect([Link]());​
// Result: ["Charlie", "Bob", "Alice"]​

// Sorting by length​
List<String> sortedByLength = [Link]()​

.sorted([Link](String::length))​
.collect([Link]());​
// Result: ["Bob", "Alice", "Charlie"]​

63. Difference between forEach() and map()?


●​ map():
○​ It is an intermediate operation.
○​ Its purpose is to transform data. It takes an element, applies a function to it, and
returns a new element.
○​ It returns a new stream of the transformed elements.
○​ Example: [Link](n -> n * 2) transforms each number into its double.
●​ forEach():
○​ It is a terminal operation.
○​ Its purpose is to consume data. It performs an action on each element but does not
return anything (void).
○​ It does not return a stream. It marks the end of the stream pipeline.
○​ Example: [Link]([Link]::println) prints each element.
You use map when you want to change the elements in the stream and continue processing
them. You use forEach when you want to do something with each element at the end of the
pipeline (like printing them or adding them to another collection).

Optional Class
64. What is the purpose of the Optional class?
The Optional<T> class, introduced in Java 8, is a container object which may or may not contain
a non-null value. Its primary purpose is to provide a better way to handle null values and avoid
NullPointerException. Instead of returning null from a method, you can return an Optional that
explicitly communicates that a value might be absent. This forces the caller to consciously
handle the case where a value is not present, leading to more robust code.

65. How to avoid NullPointerException using Optional?


Optional provides several methods that allow you to handle the absence of a value gracefully
without explicit if (obj != null) checks. By using methods like isPresent(), ifPresent(), orElse(),
and orElseThrow(), you can define what should happen if a value is present or absent, thus
preventing a NullPointerException that would occur if you tried to call a method on a null
reference.

66. What are the commonly used methods in Optional?

67. of(), ofNullable(), isPresent(), ifPresent(), orElse(), orElseGet(),


orElseThrow()
●​ Creation Methods:
○​ [Link](value): Creates an Optional with a specific non-null value. Throws
NullPointerException if the value is null.
○​ [Link](value): Creates an Optional that wraps the given value if it's
non-null, or returns an empty Optional if the value is null.
○​ [Link](): Returns an empty Optional.
●​ Checking Methods:
○​ isPresent(): Returns true if a value is present, otherwise false.
○​ ifPresent(Consumer<T> consumer): If a value is present, it executes the given
consumer with the value, otherwise does nothing.
●​ Value Retrieval Methods:
○​ get(): Returns the value if present, otherwise throws NoSuchElementException.
(Use with caution).
○​ orElse(T other): Returns the value if present, otherwise returns the specified other
default value.
○​ orElseGet(Supplier<T> other): Returns the value if present, otherwise returns the
result produced by the given supplier.
○​ orElseThrow(Supplier<X> exceptionSupplier): Returns the value if present,
otherwise throws an exception produced by the provided supplier.

68. Difference between orElse() and orElseGet()?


Both methods provide a default value if the Optional is empty, but they differ in their execution:
●​ orElse(T other): The default object passed as an argument is always created, whether
the Optional contains a value or not. This can be inefficient if creating the default object is
a heavy operation.
●​ orElseGet(Supplier<T> other): The Supplier function is only invoked to create the
default object if the Optional is empty.
Use orElse() when the default value is a pre-computed constant or a cheap-to-create object.
Use orElseGet() when creating the default value is computationally expensive, as it will only be
created when needed.
// orElse(): getDefaultValue() is always called​
String value1 = [Link](getDefaultValue());​

// orElseGet(): getDefaultValue() is only called if optional is empty​
String value2 = [Link](() -> getDefaultValue());​

Method and Constructor References


69. What is method reference in Java 8?
A method reference is a shorthand syntax for a lambda expression that executes just one
method. It allows you to refer to a method without invoking it. Method references make the code
more compact and readable by referring to a method by its name. They are often used in
combination with the Stream API.
The syntax is ClassName::methodName or objectReference::methodName.

70. Types of method references:


There are four main types of method references:

71. Reference to a static method


●​ Syntax: ContainingClass::staticMethodName
●​ Example: A lambda like s -> [Link](s) can be written as Integer::parseInt.

72. Reference to an instance method of a particular object


●​ Syntax: containingObject::instanceMethodName
●​ Example: A lambda like () -> [Link]() can be written as
myObject::doSomething.

73. Reference to an instance method of an arbitrary object of a


particular type
●​ Syntax: ContainingType::methodName
●​ Example: A lambda like (s, t) -> [Link](t) can be written as
String::compareToIgnoreCase. Here, the first parameter of the lambda becomes the
target of the method call.

74. Reference to a constructor


●​ Syntax: ClassName::new
●​ Example: A lambda like () -> new ArrayList<>() can be written as ArrayList::new.

74. How is method reference different from lambda expressions?


●​ A method reference is essentially a more concise way to write a specific type of lambda
expression.
●​ You can only use a method reference if the lambda expression's body consists of a single
method call with arguments that match the lambda's parameters.
●​ A lambda expression is more general. It can contain any block of code, not just a single
method call.
Essentially, method references are syntactic sugar for simple lambda expressions, improving
code clarity when the lambda's only purpose is to call an existing method.

Default and Static Methods in Interfaces


75. What are default methods in interfaces?
A default method is a method in an interface that has an implementation. It is declared with the
default keyword. They allow you to add new methods to existing interfaces without breaking the
classes that already implement those interfaces.

76. Why were default methods introduced in Java 8?


Default methods were introduced primarily for backward compatibility and interface
evolution. Before Java 8, if you added a new method to an interface, all implementing classes
would break because they would need to provide an implementation for the new method.
With default methods, you can add new functionality to interfaces (e.g., adding forEach to the
Iterable interface) and provide a default implementation. This allows the vast ecosystem of
existing collections to gain new functionality (like using streams) without forcing every single
collection implementation in the world to be rewritten.
77. Can a class override a default method?
Yes. A class that implements an interface can override a default method just like any other
method. If the class provides its own implementation, that implementation will be used instead
of the default one from the interface.

78. What happens in case of multiple inheritance conflicts with default


methods?
A class can implement multiple interfaces. If two or more interfaces have a default method with
the same signature, it leads to a conflict known as the "Diamond Problem".
In this case, the compiler will force the implementing class to resolve the ambiguity by
overriding the method and providing its own implementation. Inside its implementation, the
class can choose to call the default method from a specific super-interface using the syntax
[Link]().

79. What are static methods in interfaces?


Since Java 8, interfaces can also have static methods. A static method in an interface is similar
to a static method in a class. It belongs to the interface itself, not to the instances of the
implementing classes.
Key points:
●​ They are called using the interface name: [Link]().
●​ They cannot be overridden by implementing classes.
●​ They are primarily used to provide utility methods related to the interface, for example,
helper methods for creating or working with instances of the interface.

Date and Time API ([Link] package)


80. What's new in the Java 8 Date and Time API?
The [Link] package, introduced in Java 8, is a complete overhaul of the old date and time
APIs ([Link], [Link]). It was inspired by Joda-Time and addresses the many
shortcomings of the old API.
Key features:
●​ Immutability: All classes in the new API are immutable, making them thread-safe.
●​ Clarity: A clear separation between date, time, timestamp, duration, and periods.
Methods have clear, self-explanatory names (plusDays, minusHours).
●​ Time Zones: Proper and easy-to-use time zone support with ZonedDateTime and
ZoneId.
●​ Fluent API: Methods can be chained for easy manipulation.
●​ Machine and Human Time: Instant for machine-readable time and classes like
LocalDate, LocalTime for human-readable time.

81. Difference between old Date/Calendar API and new Java 8


DateTime API?
Feature Old API (Date, Calendar) New API ([Link])
Mutability Mutable (e.g., Date, Calendar Immutable. All classes are
objects can be changed). Not thread-safe.
thread-safe.
API Design Poorly designed. Date has both Clean, fluent, and intuitive API.
date and time. Months are Clear separation of concepts.
0-indexed.
Time Zones Difficult and confusing to work Straightforward and robust time
with. zone handling (ZoneId,
ZonedDateTime).
Clarity Ambiguous classes and Precise classes for specific
methods. needs (LocalDate, LocalTime,
Instant, Duration).
Formatting Requires SimpleDateFormat, A new, thread-safe
which is not thread-safe. DateTimeFormatter class.
82. What are LocalDate, LocalTime, and LocalDateTime?
These are the core classes of the new API for handling date and time without time zones.
●​ LocalDate: Represents a date without time and time zone information, like 2025-07-16.
It's useful for representing birthdays or holidays.
●​ LocalTime: Represents a time without a date and time zone, like [Link]. It's useful for
representing opening/closing times.
●​ LocalDateTime: Represents a date and time combined, but still without a time zone, like
2025-07-16T[Link].

83. How to perform date arithmetic (add/subtract days, months, etc.)?


The new API provides simple and fluent methods for date arithmetic. Since the classes are
immutable, these methods return a new object with the modified value.
LocalDate today = [Link]();​

LocalDate nextWeek = [Link](7);​
LocalDate lastMonth = [Link](1);​
LocalDate nextYear = [Link](1, [Link]);​

[Link]("Today: " + today);​
[Link]("Next Week: " + nextWeek);​

84. How to parse and format dates in Java 8?


The DateTimeFormatter class is used for parsing and formatting. It's immutable and thread-safe.
●​ Formatting (Date -> String): Use the format() method of a date/time object.
●​ Parsing (String -> Date): Use the parse() static method of the date/time class.
<!-- end list -->
LocalDateTime now = [Link]();​

// Formatting​
DateTimeFormatter formatter = [Link]("yyyy-MM-dd
HH:mm:ss");​
String formattedDateTime = [Link](formatter); // "2025-07-16
[Link]"​

// Parsing​
String dateString = "2024-01-20";​
DateTimeFormatter dateFormatter =
[Link]("yyyy-MM-dd");​
LocalDate parsedDate = [Link](dateString, dateFormatter);​

Collectors and Collect()


85. What is the purpose of the collect() method in streams?
collect() is a terminal operation in the Stream API. Its purpose is to perform a mutable
reduction on the elements of a stream. It's a versatile operation used to accumulate stream
elements into a collection (like a List, Set, or Map) or to summarize them into a single value (like
a concatenated string). It takes a Collector as an argument, which specifies how the
accumulation should work.

86. Explain [Link](), toSet(), toMap(), joining(), groupingBy()


with examples.
The [Link] class provides static factory methods for creating Collector
instances.
●​ [Link]() / toSet(): Collects stream elements into a List or Set.​
List<String> list = [Link]([Link]());​
Set<String> set = [Link]([Link]());​

●​ [Link](keyMapper, valueMapper): Collects elements into a Map. You


provide functions to extract the key and value from each element.​
Map<Integer, String> map = [Link]()​
.collect([Link](Product::getId, Product::getName));​

●​ [Link](delimiter): Joins stream elements of type String into a single string.​


String names = [Link]([Link](", ")); //
"Alice, Bob, Charlie"​

●​ [Link](classifier): Groups elements based on a classification function


and returns a Map.​
// Group products by their category​
Map<String, List<Product>> byCategory = [Link]()​
.collect([Link](Product::getCategory));​
87. How to count elements using Collectors?
While [Link]() is a direct way to count elements, you can also use a collector for this,
which is especially useful as a downstream collector in groupingBy.
●​ [Link](): Returns a Collector that counts the number of input elements.​
// Count how many products are in each category​
Map<String, Long> countByCategory = [Link]()​
.collect([Link](Product::getCategory,
[Link]()));​

88. How to group and partition data using Collectors?


●​ Grouping (groupingBy): As seen above, [Link](classifier) groups
elements into a Map based on the result of the classifier function. It's like the GROUP BY
clause in SQL.
●​ Partitioning (partitioningBy): [Link](predicate) is a special case of
grouping. It partitions the stream elements into a Map<Boolean, List<T>>. The map will
have two entries: one for true (elements that match the predicate) and one for false
(elements that don't).​
// Partition products into expensive (>= 500) and cheap (< 500)​
Map<Boolean, List<Product>> partitionedProducts =
[Link]()​
.collect([Link](p -> [Link]() >= 500));​

List<Product> expensive = [Link](true);​
List<Product> cheap = [Link](false);​

Parallel Streams
89. What are parallel streams?
A parallel stream is a stream that is processed by multiple threads concurrently. The Java
Collections Framework provides an easy way to split a data source (like a List) into multiple
parts, process each part in a separate thread, and then combine the results. This is done using
the Fork/Join framework under the hood. The goal is to leverage multi-core processors to speed
up computation.

90. How to convert a stream to parallel?


There are two simple ways to get a parallel stream:
1.​ Call the parallelStream() method on a collection.​
List<Integer> numbers = [Link](1, 2, 3, 4, 5);​
Stream<Integer> parallelStream = [Link]();​
2.​ Call the parallel() intermediate operation on an existing sequential stream.​
Stream<Integer> stream = [Link](1, 2, 3, 4, 5);​
Stream<Integer> parallelStream = [Link]();​

You can convert a parallel stream back to sequential using the .sequential() method.

91. When should you use parallel streams?


Parallel streams are not always faster. The overhead of coordinating threads can sometimes
make them slower than sequential streams. Use parallel streams when:
●​ You have a large dataset.
●​ The processing of each element is computationally expensive and can be done
independently.
●​ The operations are stateless and associative.
●​ You are running on a multi-core processor.
Tasks like searching for a prime number in a huge range are good candidates. Simple tasks like
summing a small list of integers are usually faster with a sequential stream.

92. What are the potential pitfalls of parallel streams?


1.​ Overhead: For small datasets or simple operations, the overhead of managing threads
(splitting, combining) can be greater than the benefit of parallelism.
2.​ Incorrect Results with Stateful Operations: If your lambda expressions are stateful
(i.e., they modify a shared state from outside the lambda), parallel execution can lead to
race conditions and incorrect results. Operations must be stateless.
3.​ Performance Issues with Blocking I/O: If the operations involve blocking I/O (like
network or file access), the threads in the common Fork/Join pool might get blocked,
starving other parts of your application that rely on the same pool.
4.​ Ordering: Some operations are inherently harder to parallelize if order matters. While
forEachOrdered() exists, it can diminish the performance gains of parallelism.
5.​ Debugging: Debugging parallel streams is significantly more complex than debugging
sequential ones.

You might also like