Reference
Little Dinosaur website, The numbers in this article are based on the numbers on this website.
IDs Questions Map
- “id”: 43, “question”: “Abstract class vs. Interface. When to use abstract class and when to use interface?”,
- “id”: 44, “question”: “New feature of Java 8. Give an example of how you use them in your project”,
- “id”: 45, “question”: “Comparable vs. Comparator”,
- “id”: 46, “question”: “Types of Exceptions and how do you deal with exceptions in your project?”,
- “id”: 47, “question”: “Generics and how do you use generics in your project?”,
- “id”: 48, “question”: “Java OOP 4 principles, and explain each of them”,
- “id”: 49, “question”: “If java 8 allows default method in interface, so what is the real difference between interface and abstract class?”,
- “id”: 52, “question”: “Serialization, What is Serializable and SerialVersionUID?”,
- “id”: 53, “question”: “HashMap: how does it work internally, what is hash collision”,
- “id”: 59, “question”: “What is the Functional Interface? Java 8 built in functional interface?”,
- “id”: 62, “question”: “HashTable vs. HashMap”,
- “id”: 67, “question”: “Implement a singleton”,
- “id”: 68, “question”: “Do you know about the Executor Service and Future?”,
- “id”: 69, “question”: “Explain the Factory design pattern”,
- “id”: 70, “question”: “What design patterns did you worked on before?”,
- “id”: 71, “question”: “How to custom an Exception?”,
- “id”: 72, “question”: “OutOfMemoryError vs StackOverflowError vs Memory Leak”,
- “id”: 75, “question”: “java 8: Different types of \”method reference\””,
- “id”: 78, “question”: “What is Optional?”,
- “id”: 90, “question”: “ArrayList vs LinkedList, which one to choose?”,
- “id”: 91, “question”: “How does Java class loader work?”,
- “id”: 92, “question”: “Java 8: Intermediate operator and terminal operator in stream api”,
- “id”: 95, “question”: “How do you create a thread?”,
- “id”: 98, “question”: “How does arraylist work internally?”,
- “id”: 104, “question”: “How does thread communicate/interact/share data with each other?”,
- “id”: 106, “question”: “What are the meaning of thread methods: join, wait, sleep, yield”,
- “id”: 113, “question”: “Shallow copy vs. Deep copy”,
- “id”: 115, “question”: “How does ConcurrentHashMap works?”,
- “id”: 117, “question”: “What is ConcurrentModificationException and how to handle it?”,
- “id”: 118, “question”: “Relationship between equals() and hashcode()”,
- “id”: 119, “question”: “How to make class immutable?”,
- “id”: 121, “question”: “What is the deadlock? How to FIND it? How to avoid it?”,
- “id”: 127, “question”: “Difference between HashSet and TreeSet, HashMap and TreeMap”,
- “id”: 131, “question”: “When implements Serializable, what if you don’t define the serialVersionUID? What if you remove it?”,
- “id”: 132, “question”: “What is fail-fast and fail-safe?”,
- “id”: 135, “question”: “Map vs. FlatMap”,
- “id”: 138, “question”: “HashMap vs LinkedHashMap and HashSet vs LinkedHashSet”,
- “id”: 139, “question”: “why using static and why not?”,
- “id”: 163, “question”: “what is SOLID principle?”,
- “id”: 295, “question”: “How to make a global count in multithreading environment”,
- “id”: 296, “question”: “Difference: Synchronized, ThreadLocal, Volatile, AtomicInteger”,
- “id”: 303, “question”: “How does string works? Why is String immutable in Java?”,
- “id”: 310, “question”: “What’s Garbage collection types and Whats new in java 8? “,
- “id”: 321, “question”: “Java switch statement has too many cases, how to improve it”,
- “id”: 330, “question”: “New features in java 11 and 17”,
- “id”: 332, “question”: “Difference: Sleep and Wait”,
- “id”: 343, “question”: “Suppose when you have Employee class to store data, when do you use list or map”,
- “id”: 344, “question”: “Difference: Arraylist vs LinkedList, when to use which”,
- “id”: 363, “question”: “Difference runnable vs callable”,
- “id”: 364, “question”: “Difference future vs completableFuture”,
- “id”: 371, “question”: “JDK vs JRE vs JVM”,
- “id”: 372, “question”: “What is externalization and its difference with serialization”,
- “id”: 373, “question”: “How do you implement a deadlock”,
- “id”: 374, “question”: “Synchronized method vs Synchronized block”,
- “id”: 375, “question”: “Difference between Iterator and Enumeration”,
- “id”: 448, “question”: “What is proxy design pattern”,
46-Types of Exceptions
Java Exception hierarchy:
throwable
- error
- error is like system error which can NOT be handled by program, like OutOfMemoryError, StackOverflowError etc.
exception.
Exception – compile/checked exception + runtime/unchecked exception. Checked exception can be handled using try catch block, like the SQLException, Thread InterrupttedException; Unchecked exception happens in runtime, like NullPointerException, IndexOutOfBoundException etc.
self defined exception: Just extend the java Exception class and define your own constructor and message
- error
- How to deal with exception
- Java: use Try/Catch/Finally block or use Throws on method level.
- Spring: use @ExceptionHandler @ControllerAdvice on the controller level to catch exception in whole application
43-Abstract class vs. Interface
Difference Between Interface and Abstract Class in Java
In Java, interfaces and abstract classes are both used to define templates or contracts for other classes, but they have distinct purposes and characteristics. Below is a comparison with examples to clarify their differences.
Key Differences
Feature | Interface | Abstract Class |
---|---|---|
Keyword | interface | abstract |
Inheritance | A class can implement multiple interfaces. | A class can extend only one abstract class. |
Method | Implementation Methods are abstract by default (except default methods, which can have implementations). |
Can contain both abstract and concrete (regular) methods. |
Fields | Can only have public static final constants. |
Can have instance variables. |
Constructors | Not allowed. | Can have constructors. |
Default Access Level | Methods are implicitly public . |
Methods can have public , protected , or package-private access. |
Use Case | Defines a contract or capability. | Defines shared characteristics or behavior. |
Example 1: Using an Interface
An interface is used to define a set of behaviors (a contract) that a class must implement.
1 | // Define an interface |
Example 2: Using an Abstract Class
An abstract class is used when you want to share common code among related classes while still requiring subclasses to provide specific implementations.
1 | // Define an abstract class |
When to Use:
- Interface:
- Use an interface to define a set of behaviors or capabilities that a class must adhere to, without concerning how they are implemented.
- Example: The Runnable interface defines the ability to be run in a thread.
- Abstract Class:
- Use an abstract class to represent shared characteristics or behavior while allowing subclasses to override specific parts.
- Example: The HttpServlet abstract class provides a framework for handling HTTP requests while letting you implement methods like doGet() or doPost().
44-New feature of Java 8
Java 8 has several new features. Here are some of them along with examples of how they can be used in a project:
Lambda Expressions
Lambda expressions enable a more concise way to represent code blocks that can be passed to methods or stored in variables.
Example: In an e-commerce project, filtering a list of products by price. Suppose there is a Product
class with a price
field.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Product {
private double price;
private String name;
public Product(double price, String name) {
this.price = price;
this.name = name;
}
public double getPrice() {
return price;
}
}
public class Main {
public static void main(String[] args) {
List<Product> products = new ArrayList<>();
products.add(new Product(100, "Product1"));
products.add(new Product(200, "Product2"));
products.add(new Product(150, "Product3"));
// Use Lambda expression to filter products with price greater than 150
List<Product> filteredProducts = products.stream()
.filter(product -> product.getPrice() > 150)
.collect(Collectors.toList());
filteredProducts.forEach(product -> System.out.println(product.getPrice()));
}
}
Method References
Method references provide a more concise way to refer to existing methods.
Example: In a logging project, there is a Logger
class with a logMessage
method for logging messages.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import java.util.Arrays;
import java.util.List;
class Logger {
public static void logMessage(String message) {
System.out.println("Logging: " + message);
}
}
public class Main {
public static void main(String[] args) {
List<String> messages = Arrays.asList("Message1", "Message2", "Message3");
// Use method reference to apply the logging method to each message
messages.forEach(Logger::logMessage);
}
}
Stream API
The Stream API is used for performing functional operations on collections, such as filtering, mapping, and reducing.
Example: In a data analysis project, calculating the total salary of employees. Suppose there is an Employee
class with a salary
field.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import java.util.ArrayList;
import java.util.List;
class Employee {
private double salary;
public Employee(double salary) {
this.salary = salary;
}
public double getSalary() {
return salary;
}
}
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(5000));
employees.add(new Employee(6000));
employees.add(new Employee(7000));
// Use Stream API to calculate the total salary of employees
double totalSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
System.out.println("Total salary: " + totalSalary);
}
}
Optional Class
The Optional class is used to solve the problem of null pointer exceptions and handle potentially null values more gracefully.
Example: In a user management system, getting a user’s email address. Suppose the User
class has a getEmail
method that might return null
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import java.util.Optional;
class User {
private String email;
public User(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
public class Main {
public static void main(String[] args) {
User user = new User("example@example.com");
// Use Optional class to safely get the user's email address
Optional<String> emailOptional = Optional.ofNullable(user.getEmail());
String email = emailOptional.orElse("default@example.com");
System.out.println("Email: " + email);
}
}
67-Implement a singleton
1 | import java.io.Serializable; |
Here is the explanation of this Java code in English:
- Package Import:
import java.io.Serializable;
: Imports theSerializable
interface, which is used to mark a class as serializable, meaning that objects of this class can be converted into a byte stream for storage or transmission.- In a
Singleton
class, implementing theSerializable
interface is to ensure that the Singleton pattern remains a singleton during serialization and deserialization. - If there is no
readResolve
method, a new object will be created during deserialization, which will break the Singleton pattern. Because the deserialization mechanism will create a new object through reflection instead of using the existing singleton instance. - When there is a
readResolve
method, during deserialization, thereadResolve
method will be called and it returns the singleton instance (obtained through thegetInstance
method), thus ensuring that the same singleton object is obtained after serialization and deserialization. - If the serialization - related content is not handled properly, multiple “singleton” objects may appear during deserialization, which violates the original intention of the Singleton pattern design. For example, suppose there is no
readResolve
method, each deserialization will create a newSingleton
object instead of reusing the existing singleton object.
- In a
- Class Definition:
public class Singleton implements Cloneable, Serializable
: Defines a public class namedSingleton
that implements theCloneable
interface and theSerializable
interface. Implementing theCloneable
interface usually indicates that objects of this class can be cloned (although in this example, theclone
method is overridden to disallow cloning), and implementing theSerializable
interface indicates that objects of this class can be serialized.
- Static Member Variable:
private static volatile Singleton instance;
: Defines a private static variableinstance
of typeSingleton
. Thevolatile
keyword is used to ensure thread-safe access to the variable in a multi-threaded environment. This prevents certain visibility and reordering issues that can occur in a multi-threaded context, making sure that changes made by one thread are visible to other threads immediately.
- Private Constructor:
private Singleton() {}
: Declares a private constructor. This ensures that theSingleton
class cannot be instantiated directly from outside the class. This is a common practice in implementing the Singleton design pattern, as it restricts the creation ofSingleton
objects to within the class itself.
- Static Method to Get Instance:
public static Singleton getInstance() {... }
: Defines a public static methodgetInstance
which is used to obtain the single instance of theSingleton
class.if (instance == null) {... }
: Checks if theinstance
variable isnull
. If it is, then the code enters the synchronized block.synchronized (Singleton.class) {... }
: Uses a synchronized block with theSingleton.class
object as the lock. This ensures that only one thread can execute the code inside the block at a time, preventing multiple threads from creating multiple instances of theSingleton
class.if (instance == null) { instance = new Singleton(); }
: Inside the synchronized block, the code checks again ifinstance
isnull
(this is known as double-checked locking) before creating a new instance ofSingleton
. This is done to avoid unnecessary synchronization overhead. Once the instance is created, subsequent calls togetInstance
will simply return the already created instance.
- Clone Method Override:
@Override protected Object clone() throws CloneNotSupportedException {... }
: Overrides theclone
method from theCloneable
interface. Here, it throws aCloneNotSupportedException
with a message indicating that cloning of theSingleton
object is not allowed. This is done to maintain the Singleton property, as we do not want multiple instances created through cloning.
- Read Resolve Method:
protected Object readResolve() { return getInstance(); }
: ThereadResolve
method is used during deserialization. When an object is deserialized, this method is called. Here, it returns the instance obtained from thegetInstance
method, ensuring that deserialization does not break the Singleton property. Instead of creating a new instance during deserialization, it returns the existing singleton instance, thus maintaining the Singleton guarantee.
In summary, this code implements the Singleton design pattern in Java, which ensures that only one instance of the Singleton
class exists throughout the application. It uses double-checked locking for thread-safe lazy initialization of the instance, prevents cloning, and ensures that deserialization returns the existing singleton instance rather than creating a new one. This is a common and robust way of implementing the Singleton pattern in Java, taking into account thread safety and serialization issues.
68-364-Executor Service and Future and CompletableFuture
Benefits of using Executor Service and Future:
- Resource Management:
ExecutorService
manages threads efficiently, reducing the overhead of thread creation and destruction. - Asynchronous Execution: Allows tasks to be executed in parallel, improving the performance of applications by leveraging multiple threads.
- Result Handling:
Future
provides a way to handle the results of asynchronous tasks, including waiting for the result, checking if the task is completed, and handling exceptions.
In summary, ExecutorService
simplifies thread management and task execution, while Future
provides a mechanism to interact with the results of asynchronous tasks. Together, they are powerful tools for writing concurrent and asynchronous Java programs, helping to improve performance and code readability.
Executor Service
- The
ExecutorService
is an interface in Java that provides a higher-level abstraction for executing tasks asynchronously compared to using raw threads. It is part of thejava.util.concurrent
package. - It manages a pool of threads, which can be used to execute
Runnable
orCallable
tasks. By using anExecutorService
, you don’t have to deal with the low-level details of thread creation, management, and destruction. - You can submit tasks to the
ExecutorService
, and it will handle scheduling and execution of those tasks using its thread pool. - Some commonly used implementations of
ExecutorService
includeThreadPoolExecutor
andScheduledThreadPoolExecutor
. Example of creating an
ExecutorService
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) {
// Creates a fixed-size thread pool with 5 threads
ExecutorService executorService = Executors.newFixedThreadPool(5);
// Submits a Runnable task
executorService.execute(() -> {
System.out.println("Task executed by thread: " + Thread.currentThread().getName());
});
// Shuts down the executor service after all tasks are completed
executorService.shutdown();
}
}In the code above:
Executors.newFixedThreadPool(5)
creates a fixed-size thread pool with 5 threads.executorService.execute()
submits aRunnable
task to the executor service for execution. TheRunnable
task is defined using a lambda expression, which simply prints the name of the thread that executes the task.executorService.shutdown()
shuts down the executor service after all submitted tasks have completed.
Future
- The
Future
interface represents the result of an asynchronous computation. It is used in conjunction withExecutorService
when you submit aCallable
task. - A
Callable
is similar to aRunnable
, but it can return a result and throw an exception. - When you submit a
Callable
to anExecutorService
, it returns aFuture
object, which you can use to check if the computation is done, wait for the computation to complete, and retrieve the result of the computation. Example of using
Future
withExecutorService
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
// Submits a Callable task
Future<Integer> future = executorService.submit(new Callable<Integer>() {
public Integer call() throws Exception {
// Simulates some computation
Thread.sleep(2000);
return 42;
}
});
try {
// Waits for the task to complete and gets the result
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executorService.shutdown();
}
}In the code above:
executorService.submit()
is used to submit aCallable<Integer>
task. TheCallable
task simulates some computation (in this case, it sleeps for 2 seconds and then returns the value 42).future.get()
blocks the calling thread until the computation is completed and returns the result. If the computation throws an exception, it will be wrapped in anExecutionException
.InterruptedException
is thrown if the waiting thread is interrupted while waiting for the result.
CompletableFuture
Enhanced Functionality:
CompletableFuture
is introduced in Java 8. It implementsFuture
and provides additional functionality for chaining asynchronous operations, combining multiple futures, and handling exceptions.It allows you to perform actions upon completion, combine multiple futures, and transform results.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// Simulate a long-running task
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 42;
});
// Do other work while the future is being computed
System.out.println("Doing other work...");
// Chain another action upon completion
CompletableFuture<String> resultFuture = future.thenApply(result -> "Result: " + result);
// Block until the final result is available
String result = resultFuture.get();
System.out.println(result);
/* result print:
Doing other work...
Result: 42
*/
}
}Explanation:
CompletableFuture.supplyAsync(() -> {... });
: Creates aCompletableFuture
that runs the given task asynchronously.future.thenApply(result -> "Result: " + result);
: Chains another action to theCompletableFuture
.resultFuture.get();
: Blocks until the final result is available.
Key Differences
- Chaining and Composing:
- Future: Does not support chaining or combining multiple asynchronous tasks.
- CompletableFuture: Allows chaining of operations using methods like
thenApply
,thenCompose
,thenCombine
, etc., enabling functional composition of asynchronous tasks.
- Exception Handling:
- Future: Limited to
get()
which throws checked exceptions. - CompletableFuture: Provides methods like
exceptionally
andhandle
for better exception handling in asynchronous tasks.
- Future: Limited to
- Completion Control:
- Future: You have to wait for the future to complete using
get()
. - CompletableFuture: Allows you to complete the future manually using
complete()
orcompleteExceptionally()
.
- Future: You have to wait for the future to complete using
Best Practices
- Use Future: For simple asynchronous tasks where you only need to wait for a result.
- Use CompletableFuture: For complex asynchronous workflows, combining multiple tasks, and performing operations upon completion of tasks.
- By using
CompletableFuture
, you can write more readable and powerful asynchronous code, leveraging the functional programming features of Java 8 and later.
- By using
70-Factory Pattern
- Factory Pattern
- This factory pattern allows you to create objects without exposing the instantiation logic to the client. The client only needs to interact with the
ProductFactory
and provide the product type, and the factory takes care of creating the appropriate product. This promotes loose coupling and makes the code more modular and maintainable.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56// Product interface
interface Product {
void show();
}
// Concrete Product A
class ConcreteProductA implements Product {
public void show() {
System.out.println("This is ConcreteProductA");
}
}
// Concrete Product B
class ConcreteProductB implements Product {
public void show() {
System.out.println("This is ConcreteProductB");
}
}
// Factory class
class ProductFactory {
public Product createProduct(String productType) {
if (productType.equalsIgnoreCase("A")) {
return new ConcreteProductA();
} else if (productType.equalsIgnoreCase("B")) {
return new ConcreteProductB();
} else {
throw new IllegalArgumentException("Invalid product type: " + productType);
}
}
}
// Main class to demonstrate the factory pattern
public class FactoryPatternExample {
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
// Create product A
Product productA = factory.createProduct("A");
productA.show();
// Create product B
Product productB = factory.createProduct("B");
productB.show();
try {
// Try to create an invalid product
Product invalidProduct = factory.createProduct("C");
invalidProduct.show();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
- This factory pattern allows you to create objects without exposing the instantiation logic to the client. The client only needs to interact with the
70-Obeserver Pattern
- Observer Pattern
- This code demonstrates the Observer Pattern, which allows a subject to maintain a list of observers and notify them of any state changes. It promotes loose coupling between the subject and the observers, making it easy to add or remove observers without modifying the subject’s code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77import java.util.ArrayList;
import java.util.List;
// Observer interface
interface Observer {
void update(String message);
}
// Subject interface
interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(String message);
}
// Concrete Subject
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// Concrete Observer A
class ConcreteObserverA implements Observer {
public void update(String message) {
System.out.println("ConcreteObserverA received message: " + message);
}
}
// Concrete Observer B
class ConcreteObserverB implements Observer {
public void update(String message) {
System.out.println("ConcreteObserverB received message: " + message);
}
}
// Main class to demonstrate the Observer Pattern
public class ObserverPatternExample {
public static void main(String[] args) {
// Create subject
ConcreteSubject subject = new ConcreteSubject();
// Create observers
Observer observerA = new ConcreteObserverA();
Observer observerB = new ConcreteObserverB();
// Attach observers to the subject
subject.attach(observerA);
subject.attach(observerB);
// Notify observers
subject.notifyObservers("Hello, Observers!");
// Detach one observer
subject.detach(observerB);
// Notify remaining observer
subject.notifyObservers("Goodbye, Observers!");
}
}
- This code demonstrates the Observer Pattern, which allows a subject to maintain a list of observers and notify them of any state changes. It promotes loose coupling between the subject and the observers, making it easy to add or remove observers without modifying the subject’s code.
448-Proxy Pattern
The Proxy Design Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. It is used to control access to the real object, add additional functionality, or provide a more efficient way of accessing the object. Here’s a detailed explanation:
Structure of Proxy Design Pattern
- Subject: This is an interface that defines the common interface for the RealSubject and Proxy.
- RealSubject: This is the actual object that the proxy represents.
- Proxy: This is the object that controls access to the RealSubject. It has a reference to the RealSubject and implements the Subject interface.
1 | interface Image { |
Explanation:
- Interface
Image
:interface Image
defines thedisplay()
method that bothRealImage
andProxyImage
will implement.
- RealSubject
RealImage
:RealImage
implementsImage
.- The
RealImage
constructor loads the image from disk when an instance is created. - The
display()
method displays the image.
- Proxy
ProxyImage
:ProxyImage
also implementsImage
.ProxyImage
holds a reference toRealImage
.- In the
display()
method ofProxyImage
, ifrealImage
is not instantiated, it creates aRealImage
instance. - Then, it calls the
display()
method ofRealImage
.
Use Cases
- Remote Proxy: Used to represent an object that exists in a different address space, like a remote object in a distributed system.
- Virtual Proxy: Used to create expensive objects on demand. For example, loading images only when they are needed to be displayed.
- Protection Proxy: Used to control access to the real object, providing authentication or authorization.
Benefits
- Lazy Loading: Objects can be loaded only when they are needed, improving performance.
- Access Control: Provides a way to control access to the real object, adding security or authorization.
- Enhanced Functionality: Proxies can add additional functionality, like logging or caching, without modifying the real object.
Example of Usage
1 | public class ProxyPatternExample { |
Explanation:
- We create a
ProxyImage
instance with the file name “test.jpg”. - The first time
display()
is called,RealImage
is instantiated and the image is loaded and displayed. - The second time
display()
is called, the already instantiatedRealImage
is used, avoiding reloading.
135-map vs. flatMap
In functional programming, Map
and FlatMap
are two commonly used higher-order functions, especially in languages like Java, Scala, Python, and JavaScript. Here’s a detailed explanation of both:
Map
Purpose: The
Map
function takes a function and applies it to each element of a collection, transforming each element into a new element. The result is a collection of the same size, where each element has been transformed by the function.1
2
3
4
5
6
7
8
9
10
11
12
13
14import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MapExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Map each integer to its square
List<Integer> squaredNumbers = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squaredNumbers); // [1, 4, 9, 16, 25]
}
}- Explanation:
- We have a list of integers
numbers
. - We use the
stream()
method to convert the list into a stream. - The
map()
function takes a lambda expressionn -> n * n
, which squares each element. - The
collect(Collectors.toList())
method collects the transformed elements back into a list.
- We have a list of integers
- Explanation:
FlatMap
Purpose: The
FlatMap
function takes a function that returns a collection for each element in the original collection. It then flattens all these collections into a single collection. It is used when you have a collection of collections and want to transform them into a single collection.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapExample {
public static void main(String[] args) {
List<List<Integer>> numberLists = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
// FlatMap to convert a list of lists into a single list
List<Integer> flattenedNumbers = numberLists.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
System.out.println(flattenedNumbers); // [1, 2, 3, 4, 5, 6]
}
}- Explanation:
- We have a list of lists of integers
numberLists
. - We use
stream()
to convert the list of lists into a stream. - The
flatMap()
function takes a lambda expressionlist -> list.stream()
, which converts each inner list into a stream. - The
flatMap()
function then flattens all these streams into a single stream. - Finally,
collect(Collectors.toList())
collects the elements from the flattened stream into a single list.
- We have a list of lists of integers
- Explanation:
374-Synchronized Method vs Synchronized block
Synchronized Method
1
2
3
4
5
6
7public class SynchronizedMethodExample {
private int count = 0;
public synchronized void increment() {
count++;
}
}- Explanation:
- The
synchronized
keyword is applied directly to the method signature. - When a thread calls
increment()
, it acquires the lock on the object instance (this) of the class. - No other thread can execute any other synchronized method of the same object until the first thread completes the execution of the synchronized method.
- It’s a simple way to ensure thread safety but can lead to performance issues if the method contains non-critical code that doesn’t need synchronization.
- The
- Explanation:
Synchronized Block
1
2
3
4
5
6
7
8
9
10public class SynchronizedBlockExample {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
}- Explanation:
- A synchronized block is used within a method.
- The
synchronized (lock)
statement takes an object (in this case,lock
) as an argument. This object serves as the lock. - Only one thread can enter the synchronized block that uses the same
lock
object at a time. - This allows for more granular control over what part of the method is synchronized, which can improve performance by limiting the scope of synchronization to only the critical section.
- Explanation:
- Key Differences
- Scope of Synchronization:
- Synchronized Method: The entire method is synchronized, even if not all code within the method requires synchronization.
- Synchronized Block: Only the code within the synchronized block is synchronized, allowing other parts of the method to be executed concurrently by different threads.
- Lock Object:
- Synchronized Method: The lock object is the instance of the class itself (this) for instance methods, or the class object for static methods.
- Synchronized Block: You can specify any object as the lock, giving you flexibility in choosing the granularity of locking.
- Performance:
- Synchronized Method: May lead to unnecessary locking and reduced performance if the method contains code that does not need to be synchronized.
- Synchronized Block: Allows for more efficient locking by only synchronizing the necessary parts of the code.
- Scope of Synchronization:
- Best Practices
- Use Synchronized Method: When the entire method needs to be thread-safe and the method is relatively small.
- Use Synchronized Block: When only a part of the method needs to be synchronized, especially in longer methods where only a small part accesses shared resources.
106-Thread
Here is an explanation of some important thread methods in Java: join
, wait
, sleep
, and yield
.
join() Method
Purpose: The
join()
method is used to wait for a thread to complete its execution. It is often used when one thread needs to wait for another thread to finish before it can continue its own execution.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread 1 completed");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread1.join();
System.out.println("Main thread continues after thread1 completes");
/* result print:
Thread 1 completed
Main thread continues after thread1 completes
*/
}
}- Explanation:
- We create a new
Thread
(thread1
) which sleeps for 2 seconds and then prints a message. - We start
thread1
withthread1.start()
. - We call
thread1.join()
, which makes the main thread wait untilthread1
completes its execution. - Only after
thread1
completes, the main thread prints its message.
- We create a new
- Explanation:
wait() Method
Purpose: The
wait()
method is used to make a thread wait until some other thread notifies it. It must be called from a synchronized block or method, and it releases the lock on the object.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 is waiting");
lock.wait();
System.out.println("Thread 1 resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 notifying");
lock.notify();
}
});
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
/* result print:
Thread 1 is waiting
Thread 2 notifying
Thread 1 resumed
*/
}
}- Explanation:
- We create an object
lock
which serves as a lock for synchronization. thread1
enters a synchronized block, callswait()
, and waits.thread2
enters the same synchronized block, callsnotify()
, and wakes upthread1
.
- We create an object
- Explanation:
sleep() Method
Purpose: The
sleep()
method is used to pause the execution of a thread for a specified amount of time. The thread does not release any locks during this time.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class SleepExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
System.out.println("Thread 1 sleeping");
Thread.sleep(2000);
System.out.println("Thread 1 awake");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
/* result print:
Thread 1 sleeping
Thread 1 awake
*/
}
}- Explanation:
- We create a new
Thread
(thread1
). thread1
callsThread.sleep(2000)
, which pauses the thread for 2 seconds.
- We create a new
- Explanation:
yield() Method
Purpose: The
yield()
method is used to suggest to the thread scheduler that the current thread is willing to yield its current use of the processor. The thread scheduler is free to ignore this suggestion.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class YieldExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 1: " + i);
Thread.yield();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 2: " + i);
Thread.yield();
}
});
thread1.start();
thread2.start();
/* result print:
Thread 2: 0
Thread 2: 1
Thread 2: 2
Thread 1: 0
Thread 1: 1
Thread 1: 2
Thread 2: 3
Thread 2: 4
Thread 1: 3
Thread 1: 4
*/
}
}- Explanation:
- We create two threads (
thread1
andthread2
). - Each thread prints a message and calls
Thread.yield()
, suggesting that the scheduler can give the CPU to another thread.
- We create two threads (
- Explanation:
104-How does thread communicate/interact/share data with each other
In Java, threads can communicate, interact, and share data with each other through several mechanisms. Here’s a detailed overview of these methods:
Shared Variables
Using Volatile Variables:
- A
volatile
variable ensures that all reads and writes to the variable are directly to and from main memory, not cached by threads. It is useful for simple flags or status indicators.1
2
3
4
5
6
7
8
9
10
11public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
flag = true;
}
public boolean getFlag() {
return flag;
}
}
- A
Explanation:
private volatile boolean flag = false;
: Declares avolatile
boolean variable.setFlag()
sets theflag
totrue
.getFlag()
retrieves the value offlag
.- Changes made by one thread to
flag
are immediately visible to other threads.
Synchronization
Using Synchronized Methods and Blocks:
- Synchronization ensures that only one thread can access a synchronized method or block at a time, preventing race conditions.
1
2
3
4
5
6
7
8
9
10
11public class SharedData {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
- Synchronization ensures that only one thread can access a synchronized method or block at a time, preventing race conditions.
Explanation:
public synchronized void increment() {... }
: Synchronized method ensures thread-safe access tocount
.public synchronized int getCount() {... }
: Ensures thread-safe access when readingcount
.
Wait, Notify, and NotifyAll
Using wait(), notify(), and notifyAll():
- These methods are used in conjunction with synchronized blocks to pause and resume threads.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class WaitNotifyExample {
private boolean ready = false;
private final Object lock = new Object();
public void waitForReady() throws InterruptedException {
synchronized (lock) {
while (!ready) {
lock.wait();
}
}
}
public void setReady() {
synchronized (lock) {
ready = true;
lock.notifyAll();
}
}
}
- These methods are used in conjunction with synchronized blocks to pause and resume threads.
Explanation:
waitForReady()
waits untilready
istrue
, usinglock.wait()
.setReady()
setsready
totrue
and wakes up waiting threads usinglock.notifyAll()
.
Thread Confinement
Using ThreadLocal:
ThreadLocal
allows each thread to have its own copy of a variable.1
2
3
4
5
6
7
8public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(1);
System.out.println(threadLocal.get());
}
}
Explanation:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
: Creates aThreadLocal
variable.threadLocal.set(1);
: Sets the value for the current thread.threadLocal.get();
: Retrieves the value for the current thread.
Using Concurrent Data Structures
Using Concurrent Collections:
- Java provides concurrent collections like
ConcurrentHashMap
,CopyOnWriteArrayList
, etc., which are thread-safe.1
2
3
4
5
6
7
8
9import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
System.out.println(map.get("key"));
}
}
- Java provides concurrent collections like
Explanation:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
: Creates a thread-safe map.map.put("key", 1);
: Puts an entry in the map.map.get("key");
: Retrieves the value from the map.
Using Locks and Condition Variables
Using ReentrantLock and Condition:
ReentrantLock
provides more flexible locking thansynchronized
, andCondition
allows more control over waiting and signaling.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class LockConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void waitForReady() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await();
}
} finally {
lock.unlock();
}
}
public void setReady() {
lock.lock();
try {
ready = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
Explanation:
ReentrantLock lock = new ReentrantLock();
: Creates a reentrant lock.Condition condition = lock.newCondition();
: Creates a condition associated with the lock.condition.await();
makes the thread wait.condition.signalAll();
wakes up waiting threads.
By using these mechanisms, threads can communicate, interact, and share data safely and efficiently, avoiding race conditions and ensuring thread-safe access to shared resources.
121-373-deadlock
Deadlock is when you have 2 threads, Thread1 is waiting Thread2 to release lock on an object while Thread2 is waiting for Thread1 to release lock on an object. They are waiting on each other.
1 | public class ThreadLock { |
Finding Deadlocks
Thread Dump Analysis:
- You can use tools like
jstack
(part of the JDK) to take a thread dump of a running Java application. A thread dump shows the state of all threads, including which locks they hold and which locks they are waiting for. Example of using
jstack
:1
jstack <PID>
Here,
<PID>
is the process ID of the Java application.- Analyzing the thread dump:
- Look for threads that are in the
BLOCKED
state. If two or more threads are waiting on locks held by each other, it indicates a deadlock. - For example, if Thread A is waiting for a lock held by Thread B, and Thread B is waiting for a lock held by Thread A, it’s a deadlock.
- Look for threads that are in the
- You can use tools like
- Java VisualVM:
- Java VisualVM is a monitoring and profiling tool. It can detect deadlocks automatically.
- Steps:
- Start your Java application.
- Open Java VisualVM.
- Locate your application in the list of running Java processes.
- Go to the “Threads” tab.
- Look for deadlock warnings, and examine thread states and locks.
Avoiding Deadlocks
Lock Ordering:
- Always acquire locks in a consistent order across all threads.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class DeadlockAvoidance {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
// Critical section
}
}
}
public void method2() {
synchronized (lock1) {
synchronized (lock2) {
// Critical section
}
}
}
}Explanation:
- Both
method1()
andmethod2()
acquirelock1
beforelock2
. This ensures that no thread holdslock2
while waiting forlock1
.
- Both
Lock Timeout:
- Use
tryLock()
with a timeout instead ofsynchronized
orlock()
. Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutLockExample {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void method1() {
boolean lock1Acquired = false;
boolean lock2Acquired = false;
try {
lock1Acquired = lock1.tryLock(100, TimeUnit.MILLISECONDS);
lock2Acquired = lock2.tryLock(100, TimeUnit.MILLISECONDS);
if (lock1Acquired && lock2Acquired) {
// Critical section
}
} catch (InterruptedException e) {
// Handle interruption
} finally {
if (lock1Acquired) lock1.unlock();
if (lock2Acquired) lock2.unlock();
}
}
}Explanation:
tryLock(100, TimeUnit.MILLISECONDS)
tries to acquire the lock with a timeout of 100 milliseconds.- If the lock cannot be acquired within the timeout, the thread can take corrective action.
- Use
- Avoid Nested Locks:
- Minimize the use of nested locks. If possible, design your code to use a single lock or fewer nested locks.
Best Practices
- Resource Allocation Graph:
- Use a resource allocation graph to analyze potential deadlocks before implementation.
- Ensure that the graph does not contain cycles, which indicate potential deadlocks.
- Deadlock Detection Algorithms:
- Implement deadlock detection algorithms like the Banker’s algorithm in more complex systems, especially in operating systems or resource management systems.
118-Relationship between equals() and hashcode()
In Java, the equals()
method is used to compare the equality of two objects. Here’s a detailed explanation:
Default Implementation
- The
equals()
method is defined in theObject
class, which is the superclass of all classes in Java. The default implementation of
equals()
uses the==
operator, which checks if two references point to the same object (i.e., reference equality).1
2
3
4
5
6
7
8
9
10public class EqualsExample {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = obj1;
System.out.println(obj1.equals(obj2)); // false
System.out.println(obj1.equals(obj3)); // true
}
}- Explanation:
obj1.equals(obj2)
returnsfalse
becauseobj1
andobj2
are different object instances.obj1.equals(obj3)
returnstrue
becauseobj3
refers to the same object asobj1
.
- Explanation:
Overriding equals()
You should override the
equals()
method in your custom classes if you want to compare objects based on their state (content equality) rather than reference equality.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass()!= o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
}- Explanation:
if (this == o) return true;
checks if the objects are the same reference.if (o == null || getClass()!= o.getClass()) return false;
checks ifo
isnull
or not of the same class.Person person = (Person) o;
castso
toPerson
.return age == person.age && name.equals(person.name);
compares theage
andname
fields.
- Explanation:
hashCode() and equals()
- If you override
equals()
, you should also overridehashCode()
. The
hashCode()
method is used in hash-based collections likeHashMap
andHashSet
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass()!= o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}- Explanation:
hashCode()
is overridden to generate a hash code based onname
andage
.- The formula
int result = name.hashCode(); result = 31 * result + age;
is a common way to combine hash codes.
- Explanation:
Rules for equals()
- Reflexive:
x.equals(x)
should always betrue
. - Symmetric: If
x.equals(y)
istrue
, theny.equals(x)
should also betrue
. - Transitive: If
x.equals(y)
istrue
andy.equals(z)
istrue
, thenx.equals(z)
should betrue
. - Consistent: Repeated calls to
equals()
should return the same result, if the objects have not changed. - Null Check:
x.equals(null)
should always befalse
.
Using equals()
1 | public class EqualsUsage { |
- Explanation:
p1.equals(p2)
returnstrue
becausep1
andp2
have the samename
andage
(because we overrode itsequals
method above).p1.equals(p3)
returnsfalse
becausep1
andp3
have differentname
andage
.
132-What is fail-fast and fail-safe?
Here’s an explanation of fail-fast and fail-safe mechanisms in Java, particularly in the context of collections:
Fail-Fast
- Concept:
- Fail-fast iterators in Java immediately throw a
ConcurrentModificationException
if the collection is modified structurally during iteration. Structural modifications include adding or removing elements. - It is designed to fail as soon as possible to avoid unpredictable behavior due to concurrent modifications.
- Fail-fast iterators in Java immediately throw a
Example of Fail-Fast Iterator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ConcurrentModificationException;
public class test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
try {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element.equals("A")) {
// Structural modification
list.remove(element);
/*
the correct way!! When we want to remove an element, we use iterator.remove() instead of list.remove(element). This is the correct way to remove elements while iterating through the list, as the iterator's remove() method is designed to handle the modification safely, updating the internal state of the iterator and avoiding ConcurrentModificationException.
*/
}
}
} catch (ConcurrentModificationException e) {
System.out.println("ConcurrentModificationException caught: " + e.getMessage());
}
/* result print:
ConcurrentModificationException caught: null
*/
}
}- Explanation:
- We create an
ArrayList
and add elements “A”, “B”, and “C”. - We obtain an iterator using
list.iterator()
. - While iterating, we attempt to remove an element using
list.remove(element)
. - This results in a
ConcurrentModificationException
because the collection is modified during iteration.
- We create an
- Explanation:
Fail-Safe
- Concept:
- Fail-safe iterators in Java do not throw
ConcurrentModificationException
when the collection is modified structurally during iteration. - They operate on a copy of the collection, not the original collection, so changes to the original collection do not affect the iteration.
- Fail-safe iterators in Java do not throw
Example of Fail-Safe Iterator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import java.util.concurrent.CopyOnWriteArrayList;
public class FailSafeExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element.equals("B")) {
// Structural modification
list.remove(element);
}
}
}
}- Explanation:
- We create a
CopyOnWriteArrayList
. - We add elements “A”, “B”, and “C”.
- We obtain an iterator using
list.iterator()
. - We attempt to remove an element using
list.remove(element)
during iteration. - No
ConcurrentModificationException
is thrown becauseCopyOnWriteArrayList
uses a fail-safe iterator that works on a copy of the list.
- We create a
- Explanation:
Key Differences
- Concurrency Handling:
- Fail-Fast: Detects concurrent modifications and fails immediately, useful in single-threaded or low-concurrency environments.
- Fail-Safe: Does not fail when the collection is modified, suitable for concurrent environments.
- Performance:
- Fail-Fast: Generally more efficient in single-threaded environments as it does not need to create copies of the collection.
- Fail-Safe: Slower in single-threaded environments due to copying the collection, but more suitable for concurrent environments.
Best Practices
- Use Fail-Fast: When you are in a single-threaded or low-concurrency environment and want to detect concurrent modifications early.
- Use Fail-Safe: When you expect concurrent modifications and want to avoid
ConcurrentModificationException
, especially in highly concurrent environments. - Understanding the difference between fail-fast and fail-safe mechanisms helps you choose the right collection and iterator for your concurrency needs, ensuring robustness and predictability in your code.
163-what is SOLID principle
The SOLID principles are a set of five design principles in object - oriented programming and software design. These principles help in creating more maintainable, flexible, and understandable software systems. Here’s a detailed look at each of them:
- Single Responsibility Principle (SRP)**
- Definition: A class should have only one reason to change. In other words, a class should have only one job or responsibility.
- Example: Consider a class that is responsible for calculating the area of different geometric shapes (like circles, rectangles, etc.) and also responsible for saving the calculated results to a database. This violates the SRP. A better design would be to have one class for calculating the areas and another class for handling the database operations.
- Benefits: It makes the classes more focused and easier to understand and maintain. When a change is required related to a particular responsibility (say, a change in the area - calculation formula), it’s clear which class needs to be modified and other classes are not affected.
- Open - Closed Principle (OCP)**
- Definition: Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
- Example: Let’s say you have a drawing application that can draw different shapes. Initially, it can draw circles and rectangles. If you want to add a new shape like a triangle, you should be able to do it without modifying the existing drawing code for circles and rectangles. This can be achieved through inheritance or interfaces. For instance, you can have an abstract
Shape
class with adraw()
method, and concrete classes forCircle
,Rectangle
, andTriangle
that implement thedraw()
method. - Benefits: It allows for easy addition of new functionality without the risk of breaking the existing code that has already been tested and is working. This leads to more stable and maintainable software over time.
- Liskov Substitution Principle (LSP)**
- Definition: Subtypes must be substitutable for their base types. In other words, if you have a program that is using a base class, you should be able to substitute a derived class in its place without changing the correctness of the program.
- Example: Consider a base class
Vehicle
with a methodstartEngine()
. A subclassCar
inherits fromVehicle
. If theCar
class redefines thestartEngine()
method in such a way that it violates the expected behavior (for example, thestartEngine()
method in theCar
class throws an exception every time it’s called, while in theVehicle
class it starts the engine successfully most of the time), then it violates the LSP. - Benefits: It ensures that the inheritance hierarchy is used in a way that is consistent with the expected behavior. This helps in building more reliable and understandable object - oriented hierarchies.
- Interface Segregation Principle (ISP)**
- Definition: Clients should not be forced to depend on interfaces they do not use. Instead of having a large, monolithic interface, it’s better to have smaller, more specific interfaces.
- Example: Suppose you have an interface
Worker
that has methods likework()
,takeBreak()
,attendMeeting()
, anddoPaperwork()
. Now, consider a classManualLaborer
that only needs to implement thework()
method and doesn’t need to deal withdoPaperwork()
etc. Having a singleWorker
interface forces theManualLaborer
class to implement methods it doesn’t need. A better approach would be to split theWorker
interface into smaller interfaces likePhysicalWorker
(withwork()
method) andOfficeWorker
(withdoPaperwork()
,attendMeeting()
etc.) - Benefits: It reduces the coupling between different parts of the system. Classes only need to implement the interfaces that are relevant to them, which makes the code more modular and easier to understand and maintain.
- Dependency Inversion Principle (DIP)**
- Definition:
High-level
modules should not depend onlow-level
modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. - Example: Consider a
high-level
module like aUserController
in a web application that depends on alow-level
module like aUserRepository
which is responsible for database operations. Instead of theUserController
directly depending on theUserRepository
, they both can depend on an abstraction like anIUserRepository
interface. TheUserRepository
class implements this interface, and theUserController
uses the methods defined in the interface. This way, if you want to change the way user data is stored (say, from a SQL database to a NoSQL database), you only need to change the implementation of theIUserRepository
interface, and theUserController
remains unaffected. - Benefits: It makes the code more flexible and easier to test. By depending on abstractions, different implementations can be swapped in and out without major changes to the
high-level
modules.
- Definition:
What’s Garbage collection types and Whats new about GC in java 8
Reference: https://zhuanlan.zhihu.com/p/25539690
compact version
- Types of GC:
- Serial Garbage Collector: Uses only single thread to perform garbage collection. Good for small sized heaps.
- Parallel Garbage Collector: Use multiple threads to in parallel. Good for multi-core systems, like systems with high throughput.
- CMS (Concurrent) Garbage Collector: perform GC concurrently with application execution. Used for system requires very low-latency.
- G1(Garbage-First) Garbage Collector: It is to balance between low-latency and high throughput. It divides heap into regions and perform GC with each region.
- What’s new in Java:
- MetaSpace: Preivouly java use fixed size for Permanent Generation (store metadata and internal code), java 8 introduces metaspace which is flexible based on system memeory.
- G1 is made as default GC for large heaps.
- Java allows you to choose different GC algorithms based on your JVM version. Common GC options include -XX:+UseSerialGC, -XX:+UseParallelGC, -XX:+UseConcMarkSweepGC, and -XX:+UseG1GC.
detailed version
- Garbage Collection Types in Java
- Serial Garbage Collector:
- Explanation: This is the simplest and most basic garbage collector. It uses a single thread to perform garbage collection. When it runs, it stops all application threads (a “stop - the - world” event) and then scans the heap to identify and reclaim memory occupied by unreachable objects.
- Use Case: It’s suitable for small applications or applications with simple memory usage patterns and where the short pauses during garbage collection are not a critical concern. For example, a simple command - line tool that doesn’t have strict performance requirements and manages a relatively small amount of data.
- Parallel Garbage Collector:
- Explanation: Also known as the throughput collector, it uses multiple threads to perform garbage collection. Similar to the serial collector, it also causes “stop - the - world” pauses. However, due to the use of multiple threads, it can generally complete the garbage collection process more quickly than the serial collector, especially on multi - core machines. The heap is divided into generations (young and old), and it focuses on the young generation first. It uses a copying algorithm for the young generation and a mark - sweep - compact algorithm for the old generation.
- Use Case: Ideal for applications where high throughput is the main objective. For example, in a batch - processing application where the focus is on completing a large number of tasks in a short time, and a short pause for garbage collection is acceptable to achieve better overall performance.
- CMS (Concurrent - Mark - Sweep) Garbage Collector:
- Explanation: The CMS garbage collector aims to reduce the pause times during garbage collection. It attempts to perform most of its work concurrently with the application threads. It has two main phases (marking and sweeping) that run concurrently with the application. The marking phase identifies live objects, and the sweeping phase reclaims memory occupied by unreachable objects. However, it still has some short “stop - the - world” pauses during critical points in the process, such as at the start and end of the marking and sweeping phases.
- Use Case: Suitable for applications that require low - latency and can’t afford long pauses. For example, in a web application where user requests need to be served quickly and a long garbage collection pause could lead to a poor user experience.
- G1 (Garbage - First) Garbage Collector:
- Explanation: The G1 garbage collector divides the heap into multiple regions of equal size. It focuses on regions that have the most garbage (hence the name “Garbage - First”). It also tries to balance the pause times by predicting which regions are likely to fill up quickly and prioritizing them for collection. It uses a combination of concurrent and parallel techniques to achieve efficient garbage collection.
- Use Case: Ideal for applications with large heaps and where there is a need to balance throughput and pause times. For example, in a data - intensive application that manages a large amount of data in memory and requires both good performance and relatively short pauses to handle incoming requests.
- Serial Garbage Collector:
- New GC - Related Features in Java 8
- Metaspace: In Java 8, the permanent generation (PermGen) was removed and replaced with metaspace.
- Explanation: PermGen was used to store metadata such as class definitions, method data, and constant pool information. It had a fixed - size limit and was a common source of memory - related issues, such as
OutOfMemoryError
due to the inability to load more classes. Metaspace, on the other hand, is a native memory area. It can grow and shrink dynamically, which reduces the likelihood of running out of memory due to class - loading - related issues. The garbage collection of metaspace is more efficient as it uses the same collectors as the heap (e.g., G1 can manage metaspace regions). - Benefit: This change provides more flexibility in handling class - loading and metadata storage, making it easier to manage large - scale applications that load a large number of classes. For example, in a Java EE application server that deploys many applications and loads numerous classes, the metaspace allows for more efficient use of memory and better handling of class - unloading scenarios.
- Explanation: PermGen was used to store metadata such as class definitions, method data, and constant pool information. It had a fixed - size limit and was a common source of memory - related issues, such as
- Metaspace: In Java 8, the permanent generation (PermGen) was removed and replaced with metaspace.
363-Callable vs Runnable
In Java, both Runnable
and Callable
are used to represent tasks that can be executed concurrently, usually in the context of multi - threading, but they have several differences:
- Return Value
- Runnable:
- A
Runnable
interface does not have a return value. Therun
method of theRunnable
interface has avoid
return type. Its purpose is mainly to encapsulate a block of code that needs to be executed, such as a task that performs a simple operation like printing a message or updating a counter. - For example, consider the following
Runnable
implementation:1
2
3
4
5
6class MyRunnable implements Runnable {
public void run() {
System.out.println("This is a Runnable task.");
}
}
- A
- Runnable:
- Callable:
- A
Callable
interface is designed to return a result. Thecall
method of theCallable
interface has a generic return type (<V>
). The actual type of the return value is specified when theCallable
is implemented. - For example, here is a simple
Callable
implementation that returns an integer:1
2
3
4
5
6
7import java.util.concurrent.Callable;
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
return 42;
}
}
- A
- Return Value
- Exception Handling
- Runnable:
- The
run
method of theRunnable
interface does not throw checked exceptions. Any checked exceptions that occur within therun
method must be caught and handled inside therun
method. If an unchecked exception (such as aRuntimeException
) is thrown, it will cause the thread executing theRunnable
to terminate abnormally. - For example, if you try to access a file that doesn’t exist inside the
run
method and you don’t handle theFileNotFoundException
(a checked exception), it will result in a compilation error.
- The
- Callable:
- The
call
method of theCallable
interface can throw checked exceptions. This allows for more flexible error - handling mechanisms. The exceptions thrown from thecall
method can be propagated to the calling code and handled appropriately. - For example, if the
call
method is performing a network operation and aSocketException
(a checked exception) occurs, it can be thrown and caught in the code that submitted theCallable
task.
- The
- Runnable:
- Exception Handling
Usage with Executor Framework
Runnable:
Runnable
is often used with theExecutorService
‘sexecute
method. When you use theexecute
method to submit aRunnable
task, theExecutorService
will execute therun
method of theRunnable
in a separate thread (or reuse an existing thread from a thread pool).- For example:
1
2
3
4
5
6
7
8
9
10import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable task = new MyRunnable();
executor.execute(task);
executor.shutdown();
}
}
Callable:
Callable
is used with theExecutorService
‘ssubmit
method. When you submit aCallable
task using thesubmit
method, theExecutorService
returns aFuture
object. TheFuture
object can be used to retrieve the result of theCallable
task (using theget
method) and to manage the state of the task (such as checking if the task is done, canceling the task, etc.).- For example:
1
2
3
4
5
6
7
8
9
10
11
12import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = new MyCallable();
Future<Integer> future = executor.submit(task);
System.out.println("The result is: " + future.get());
executor.shutdown();
}
}
375-Difference between Iterator and Enumeration
- Both use to loop java collections.
- Iterator: can remove elements when iterator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import java.util.ArrayList;
import java.util.Iterator;
public class IteratorRemoveExample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
int element = iterator.next();
if (element % 2 == 0) {
iterator.remove();
}
}
System.out.println(list);
}
}
- Enumeration: cannot remove elements.
```java
import java.util.Enumeration;
import java.util.Vector;
public class EnumerationExample {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("Apple");
vector.add("Banana");
Enumeration<String> enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
}
}
52-131-When implements Serializable, what if you don’t define the serialVersionUID? What if you remove it?
What is serialVersionUID
?
serialVersionUID
is a unique identifier for a serializable class. It is used during deserialization to ensure that the sender and receiver of a serialized object have compatible versions of that class. It is stored as along
value in the serialized object.
When you don’t define serialVersionUID
Behavior:
- If you do not explicitly define a
serialVersionUID
in your serializable class, the Java runtime will automatically generate one for you at runtime based on various aspects of the class, such as the fields, methods, and other characteristics of the class. - This auto-generated
serialVersionUID
is computed using a complex algorithm that takes into account the class name, the interfaces it implements, and other factors.
- If you do not explicitly define a
Implications:
- Compatibility issues: Any change in the class structure (e.g., adding or removing fields, changing the type of a field, or modifying methods) will cause the runtime to generate a different
serialVersionUID
. This means that if you serialize an object with one version of the class and then try to deserialize it with a modified version of the class, the deserialization will fail with anInvalidClassException
. Example: Consider the following serializable class:
1
2
3
4
5
6
7
8import java.io.Serializable;
class MyClass implements Serializable {
private int value;
// Constructor, getters, and setters
public MyClass(int value) {
this.value = value;
}
}If you serialize an object of
MyClass
in one version of your application, and then you modify theMyClass
by adding a new field, like this:1
2
3
4
5
6
7
8
9
10import java.io.Serializable;
class MyClass implements Serializable {
private int value;
private String name;
// Constructor, getters, and setters
public MyClass(int value, String name) {
this.value = value;
this.name = name;
}
}Then, when you try to deserialize the previously serialized object, you will get an
InvalidClassException
because the auto-generatedserialVersionUID
has changed.
- Compatibility issues: Any change in the class structure (e.g., adding or removing fields, changing the type of a field, or modifying methods) will cause the runtime to generate a different
When you remove serialVersionUID
- Behavior:
- If you initially define a
serialVersionUID
and then remove it from your class, the Java runtime will again generate a new one based on the current state of the class.
- If you initially define a
Implications:
- Compatibility issues: Similar to not defining it initially, removing the
serialVersionUID
can lead to deserialization failures. If you have serialized objects using the old version of the class with a definedserialVersionUID
and then remove it, the deserialization process will try to use the auto-generatedserialVersionUID
, which is likely to be different from the original one, resulting in anInvalidClassException
. Example: Consider this class with a defined
serialVersionUID
:1
2
3
4
5
6
7
8
9import java.io.Serializable;
class MyClass implements Serializable {
private static final long serialVersionUID = 123456789L;
private int value;
// Constructor, getters, and setters
public MyClass(int value) {
this.value = value;
}
}If you serialize objects with this version of
MyClass
, and then you remove theserialVersionUID
from the class like this:1
2
3
4
5
6
7
8import java.io.Serializable;
class MyClass implements Serializable {
private int value;
// Constructor, getters, and setters
public MyClass(int value) {
this.value = value;
}
}When you try to deserialize the previously serialized objects, you will face
InvalidClassException
as the deserialization process will use the new auto-generatedserialVersionUID
which will not match the old one.
- Compatibility issues: Similar to not defining it initially, removing the
Best Practices
Explicitly define
serialVersionUID
:- It is generally recommended to explicitly define
serialVersionUID
in your serializable classes. This way, you have more control over versioning. You can decide when a change in the class should break compatibility and when it should not. Example:
1
2
3
4
5
6
7
8
9import java.io.Serializable;
class MyClass implements Serializable {
private static final long serialVersionUID = 123456789L;
private int value;
// Constructor, getters, and setters
public MyClass(int value) {
this.value = value;
}
}If you later modify the class by adding a new field but decide that it should still be compatible with the previously serialized objects, you can keep the
serialVersionUID
the same. However, if you decide that the changes are significant enough to break compatibility, you can change theserialVersionUID
.
- It is generally recommended to explicitly define
Serialization and Deserialization Example
Here is a simple example of serializing and deserializing an object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
class MyClass implements Serializable {
private static final long serialVersionUID = 123456789L;
private int value;
public MyClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class SerializationExample {
public static void main(String[] args) {
MyClass obj = new MyClass(42);
try {
// Serialization
FileOutputStream fileOut = new FileOutputStream("object.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(obj);
out.close();
fileOut.close();
// Deserialization
FileInputStream fileIn = new FileInputStream("object.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
MyClass deserializedObj = (MyClass) in.readObject();
in.close();
fileIn.close();
System.out.println(deserializedObj.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}In this example, the
MyClass
object is serialized to a file and then deserialized. If you change theMyClass
and want to maintain compatibility, you should keep theserialVersionUID
constant. If you want to break compatibility, change theserialVersionUID
.
372-What is externalization and its difference with serialization
Serialization
- Definition: Serialization is the process of converting an object’s state into a byte stream so that it can be persisted (saved to a file, sent over a network, etc.) and later reconstructed (deserialized) to an object with the same state. In Java, the
java.io.Serializable
interface is used to mark a class as serializable. When an object of a serializable class is serialized, the JVM takes care of writing the object’s state to a stream. Example: Consider a simple
Person
class.1
2
3
4
5
6
7
8
9
10import java.io.Serializable;
class Person implements Serializable {
private String name;
private int age;
// Constructor, getters, and setters
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}How it works: When you want to serialize an object of the
Person
class, you can useObjectOutputStream
. For example:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("John", 30);
try {
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}Limitations: The serialization process is automatic and uses the default behavior provided by the JVM. This can lead to inefficiencies and security risks. For example, sensitive data might be serialized and transferred without proper handling. Also, if the class structure changes (e.g., a new field is added), deserialization of the previously serialized objects might cause issues.
- Definition: Serialization is the process of converting an object’s state into a byte stream so that it can be persisted (saved to a file, sent over a network, etc.) and later reconstructed (deserialized) to an object with the same state. In Java, the
Externalization
- Definition: Externalization is a more controlled way of handling object persistence. A class that wants to use externalization must implement the
java.io.Externalizable
interface. This interface has two methods:writeExternal
andreadExternal
. The programmer is responsible for explicitly writing and reading the object’s state to and from anObjectOutput
andObjectInput
stream. Example: Let’s modify the
Person
class to use externalization.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
class Person implements Externalizable {
private String name;
private int age;
public Person() {
// Required for externalization
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
}How it works: When you want to externalize an object of the
Person
class, you can useObjectOutputStream
andObjectInputStream
similar to serialization, but the object’s state is written and read using the custom methodswriteExternal
andreadExternal
. For example:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class ExternalizationExample {
public static void main(String[] args) {
Person person = new Person("Alice", 25);
try {
// Writing the object
FileOutputStream fileOut = new FileOutputStream("person.external");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
// Reading the object
FileInputStream fileIn = new FileInputStream("person.external");
ObjectInputStream in = new ObjectInputStream(fileIn);
Person restoredPerson = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println(restoredPerson.getName() + " " + restoredPerson.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
- Definition: Externalization is a more controlled way of handling object persistence. A class that wants to use externalization must implement the
Differences
- Control:
- Serialization: The process is mostly automatic. The JVM takes care of writing the object’s non - transient fields to the stream. The programmer has limited control over what is serialized.
- Externalization: The programmer has full control. You decide exactly what data to write and read, and in what format. This allows for more efficient and customized persistence.
- Constructor Requirement:
- Serialization: The default constructor is not required. The JVM can reconstruct the object using the serialized data.
- Externalization: A public no - argument constructor is required. This constructor is used during the deserialization process (reading the object’s state).
- Performance and Flexibility:
- Serialization: It’s a convenient option when you don’t need a high level of control. But it might serialize more data than necessary and can be less efficient in terms of space and time.
- Externalization: It’s more flexible and can lead to better performance if implemented correctly. You can choose to serialize only the essential data and in a more optimized way.
- Control:
75-Different types of “method reference”
In Java 8, there are four types of method references:
Static Method Reference
- Explanation: A static method reference refers to a static method of a class. The syntax is
ClassName::staticMethodName
. The method reference can be used as a replacement for a lambda expression that calls a static method. Example: Consider a utility class
MathUtils
with a static methodadd
that takes two integers and returns their sum.1
2
3
4
5class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}You can use a static method reference with the
BinaryOperator
interface. TheBinaryOperator
interface represents an operation that takes two operands of the same type and returns a result of that type.1
2
3
4
5
6
7
8import java.util.function.BinaryOperator;
public class StaticMethodReferenceExample {
public static void main(String[] args) {
BinaryOperator<Integer> adder = MathUtils::add;
int result = adder.apply(3, 5);
System.out.println(result);
}
}Here,
MathUtils::add
is a static method reference that is assigned to theadder
variable of typeBinaryOperator<Integer>
. Theapply
method ofadder
then calls theadd
method of theMathUtils
class.
Instance Method Reference of a Particular Object
- Explanation: This type of method reference refers to an instance method of a particular object. The syntax is
objectReference::instanceMethodName
. It’s used when you have an existing object and want to refer to one of its methods. Example: Consider a
String
object and itslength
method.1
2
3
4
5
6
7
8
9import java.util.function.Function;
public class InstanceMethodReferenceExample {
public static void main(String[] args) {
String str = "Hello";
Function<String, Integer> stringLengthFunction = str::length;
int length = stringLengthFunction.apply(str);
System.out.println(length);
}
}Here,
str::length
is an instance method reference of thestr
object. TheFunction<String, Integer>
interface’sapply
method calls thelength
method of thestr
object.
Instance Method Reference to an Instance Method of an Arbitrary Object of a Particular Type
- Explanation: The syntax is
ClassName::instanceMethodName
. This is used when the method being referred to is an instance method, and the actual object on which the method will be called will be determined later. The method reference provides the method signature, and the object is provided when the function is actually executed. Example: Consider the
compareTo
method of theString
class.1
2
3
4
5
6
7
8
9import java.util.Arrays;
import java.util.Comparator;
public class ArbitraryObjectMethodReferenceExample {
public static void main(String[] args) {
String[] strings = {"apple", "banana", "cherry"};
Arrays.sort(strings, String::compareTo);
System.out.println(Arrays.toString(strings));
}
}Here,
String::compareTo
is an instance method reference to thecompareTo
method of theString
class. TheArrays.sort
method uses this reference to compare the strings and sort them.
Constructor Reference
- Explanation: A constructor reference refers to a constructor of a class. The syntax is
ClassName::new
. It’s used to create new objects of a particular class. Constructor references are useful when you want to use a constructor as a method reference, for example, in factory methods or when populating collections with new objects. Example: Consider a simple
Person
class with a constructor that takes aString
name.1
2
3
4
5
6
7
8
9class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}You can use a constructor reference with a
Supplier
interface (which represents a function that supplies a result).1
2
3
4
5
6
7
8
9import java.util.function.Supplier;
public class ConstructorReferenceExample {
public static void main(String[] args) {
Supplier<Person> personSupplier = Person::new;
Person person = personSupplier.get();
person = personSupplier.get("John");
System.out.println(person.getName());
}
}Here,
Person::new
is a constructor reference. Theget
method of theSupplier<Person>
calls the constructor of thePerson
class to create a newPerson
object.