Questions and informations prepairing for Java Developer job interview

What are SOLID principles ?

What is Single Responsibility Principle (SRP):

A class should have only one responsibility.
It should have only one reason to change.
Benefits include easier testing, lower coupling, and better organization.
Example:

Java

public class Book {
private String name;
private String author;
private String text;
// constructor, getters, and setters
}

What is Open-Closed Principle (OCP):

Software entities (classes, modules, functions) should be open for extension but closed for modification.
Achieve this by using interfaces, abstract classes, and inheritance.
Example:
Java

interface Shape {
double area();
}
class Circle implements Shape { /* implementation / } class Rectangle implements Shape { / implementation */ }

What is Liskov Substitution Principle (LSP):

Subtypes must be substitutable for their base types without altering the correctness of the program.
Follows the “is-a” relationship.
Example:
Java

class Bird { /* common bird behavior / } class Sparrow extends Bird { / specific sparrow behavior */ }

What is Interface Segregation Principle (ISP):

Clients should not be forced to depend on interfaces they don’t use.
Split large interfaces into smaller, more specific ones.
Example:
Java

interface Worker {
void work();
}
interface Eater {
void eat();
}
class Robot implements Worker { /* implementation */ }

What is Dependency Inversion Principle (DIP):

High-level modules should not depend on low-level modules; both should depend on abstractions.
Abstractions should not depend on details; details should depend on abstractions.

// Abstraction
interface MessageSender {
void sendMessage(String message);
}

// High-level module
class NotificationService {
private MessageSender messageSender;

public NotificationService(MessageSender messageSender) {
this.messageSender = messageSender;
}
public void send(String message) {
messageSender.sendMessage(message);
}

}

// Low-level module
class EmailSender implements MessageSender {
@Override
public void sendMessage(String message) {
System.out.println(“Sending Email: ” + message);
}
}

// Another Low-level module
class SmsSender implements MessageSender {
@Override
public void sendMessage(String message) {
System.out.println(“Sending SMS: ” + message);
}
}

// Main class
public class Main {
public static void main(String[] args) {
// Use EmailSender
MessageSender emailSender = new EmailSender();
NotificationService notificationService1 = new NotificationService(emailSender);
notificationService1.send(“Hello via Email!”);

    // Use SmsSender
    MessageSender smsSender = new SmsSender();
    NotificationService notificationService2 = new NotificationService(smsSender);
    notificationService2.send("Hello via SMS!");
}

}

The MessageSender interface serves as the abstraction.
High-level module NotificationService depends on this abstraction instead of concrete implementations.
Low-level modules like EmailSender and SmsSender also depend on the abstraction by implementing the MessageSender interface.

What is Normalization of SQL

What is Normalization?
Normalization involves structuring data within a relational database to eliminate anomalies like redundancy.
It breaks down large, complex tables into smaller ones while maintaining data relationships.
Why Is Normalization Important?
Reduces Redundancy: By splitting data into smaller tables, we avoid storing the same information multiple times.
Improves Query Performance: Smaller normalized tables lead to faster query execution.
Minimizes Update Anomalies: Updates can be made without affecting other records.
Enhances Data Integrity: Ensures consistent and accurate data.
Scenarios for Normalization:
Data Integrity: Prevent inconsistencies when updating data (e.g., customer age changes).
Efficient Querying: Simplify complex queries by breaking down data.
Storage Optimization: Eliminate unnecessary redundancy to save space.

What is Microservices monolith?

What is monolith?

A monolithic application is built as a single, unified unit. It tightly couples all components (such as the client-side UI, database, and server-side application) into one code base.
Advantages:
Easy Deployment: Since it’s a single executable file or directory, deployment is straightforward.
Development Speed: Early in a project’s life, monoliths allow fast development due to their simplicity.
Performance: Centralized code can handle various functions.

What are Microservices?

Microservices break down the application into smaller, independently deployable services. Each service focuses on a specific business domain.
Advantages:
Scalability: Microservices allow scaling individual components independently.
Flexibility: Developers can choose different technologies for each service.
Isolation: Failures in one service don’t affect others.
Challenges:
Complexity: Managing multiple services requires robust orchestration.
Network Overhead: Communication between services introduces latency.
Deployment Complexity: Coordinating updates across services can be tricky.

What is Polymorphism?

Method Overriding:
When a subclass provides a specific implementation for a method that is already defined in its superclass.

Method Overloading:
When a class has multiple methods with the same name but different parameters (different number or types of arguments).

What is Private constructors use?

–Private constructors are used in utility classes or constant classes.
These classes contain only static methods or members.

–Avoiding Subclassing

— Constructor Delegation:
Private constructors allow constructor delegation, where specific constructors call a common private constructor.

— Singleton Pattern

About JVM

–Compilation:
When you compile a .java file, the Java compiler generates .class files containing bytecode (intermediate representation).
Bytecode is platform-independent and can run on any system with a compatible JVM.
–Class Loader Subsystem:
Responsible for loading classes into memory.
Reads .class files, generates binary data, and stores it in the method area.
Creates a Class object representing the loaded class in heap memory.
Example: Student s1 = new Student(); Class c1 = s1.getClass();
–Linking:
Consists of three steps:
Verification: Ensures the correctness of the .class file.
Preparation: Allocates memory for class variables and initializes them.
Resolution (optional): Resolves symbolic references to other classes or methods.
–Initialization:
Executes static initializers (e.g., static blocks) and initializes static variables.
Instance variables are initialized when an object is created.
–Execution Engine:
Converts bytecode into machine code.
Includes Just-In-Time (JIT) compilation for performance optimization.

–In summary, the JVM allows Java programs to run anywhere by interpreting bytecode and managing memory

What is Privat abstract method?

–Use Cases for Private Methods in Abstract Classes:
While abstract methods cannot be private, you can use private methods within an abstract class for various purposes:
–Helper Methods: Implement common functionality shared by other methods.
–Encapsulation: Hide implementation details from subclasses.
–Struct-like Classes: Create small classes with private methods that serve as data containers

What is Garbage collector?

–Heap Memory:
Java objects are created in the heap, a dedicated memory area.
Some objects become unreachable (no longer referenced by the program).
–Garbage Collector (GC):
The GC identifies and deletes unused objects to free up memory.
It runs automatically in the background.
Two types:
Minor GC: Removes unreachable objects in the young generation.
Major GC (Full GC): Clears objects in the old generation.
–Benefits:
Developers don’t need to explicitly delete objects.
GC ensures memory availability and prevents OutOfMemoryErrors

What is OOP?

Four Principles of OOP:
–Encapsulation: Bundling data (attributes) and methods (functions) together within a class to hide implementation details and provide a clean interface.
–Inheritance: Creating new classes based on existing ones, inheriting their attributes and behaviors. It promotes code reuse.
–Polymorphism: Allowing objects of different classes to be treated uniformly through a common interface. It enables flexibility and extensibility.
–Abstraction: Focusing on essential features while hiding unnecessary complexities. Abstraction simplifies the design and makes it more manageable.

What is ACID properties play a crucial role in ensuring the reliability and consistency of transactions.

–Atomicity:
Transactions are either executed entirely or not at all.
No partial execution: If a transaction fails midway, any changes made to the database are rolled back.
Example: Consider transferring $100 from account X to account Y. If the transaction fails after deducting from X but before adding to Y, the database remains consistent.
–Consistency:
The database must maintain integrity constraints before and after a transaction.
Example: If the total amount before a transaction is $700 (X: $500, Y: $200), it should remain the same after the transaction (X: $400, Y: $300).
–Isolation:
Multiple transactions can occur concurrently without affecting each other.
Changes made by one transaction are not visible to others until committed.
Ensures equivalent results as if transactions were executed serially.
Example: Isolating transactions prevents inconsistencies due to interleaved operations.
–Durability:
Once a transaction is committed, its changes are permanent.
Survives system failures or crashes.
Ensures data persistence.
Example: Even after a system restart, committed changes remain intact.
Remember, ACID properties provide the foundation for reliable and consistent database operations! 🌟

What is Strategy Pattern?
Description: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the algorithm to vary independently from clients that use them.
Example:
Java

// Interface for eat behavior
interface EatBehavior {
void eat();
}

// Concrete implementations of eat behavior
class NormalDiet implements EatBehavior {
@Override
public void eat() {
// Normal food
}
}

class LosingWeightDiet implements EatBehavior {
@Override
public void eat() {
// Healthy food
}
}

// Cat class with eat behavior
abstract class Cat {
EatBehavior eatBehavior;

public void eat() {
eatBehavior.eat();
}
public void setEatBehavior(EatBehavior eatBehavior) {
this.eatBehavior = eatBehavior;
}

}

// Usage
Cat cat = new Cat();
cat.setEatBehavior(new LosingWeightDiet());
cat.eat(); // Cat eats with a healthier diet

Creational Patterns:
These patterns focus on class instantiation.
Examples include

— Factory Method,
Problem Statement: Suppose we want to create different types of products (e.g., cars, bikes, cycles) without tightly coupling our code to specific implementations.
Key Components:
Product: An abstract class or interface defining common operations for the objects that the factory will create.
Concrete Products: Actual classes implementing the Product interface, representing specific types of objects.
Creator: An abstract class or interface declaring the factory method. It’s responsible for creating Product objects but delegates the actual creation to subclasses.
Concrete Creators: Subclasses of the Creator that implement the factory method. They decide which specific Concrete Product to create based on input parameters or configuration.
Factory Method: A method defined in the Creator class that creates Product objects. It’s typically declared as abstract in the Creator and implemented in the Concrete Creators.
Example in Java: Let’s say we have a Factory that produces different types of vehicles. Here’s a simplified example:
Java

// Abstract Product
interface Product {
void display();
}

// Concrete Products
class Car implements Product {
public void display() {
System.out.println(“Car created”);
}
}

class Bike implements Product {
public void display() {
System.out.println(“Bike created”);
}
}

// Creator (Factory)
interface Factory {
Product factoryMethod();
}

// Concrete Creators
class ConcreteFactoryA implements Factory {
public Product factoryMethod() {
return new Car();
}
}

class ConcreteFactoryB implements Factory {
public Product factoryMethod() {
return new Bike();
}
}

public class FactoryMethodExample {
public static void main(String[] args) {
Factory factoryA = new ConcreteFactoryA();
Product productA = factoryA.factoryMethod();
productA.display();

    Factory factoryB = new ConcreteFactoryB();
    Product productB = factoryB.factoryMethod();
    productB.display();
}

}

— Builder
The Builder Pattern is a creational design pattern that allows you to construct complex objects step by step,
separating their construction from their representation. Here’s a simple example in Java:
// Define the class you want to build
class Post {
private final String title;
private final String text;
private final String category;

Post(Builder builder) {
this.title = builder.title;
this.text = builder.text;
this.category = builder.category;
}
// Getters for properties
public String getTitle() {
return title;
}
public String getText() {
return text;
}
public String getCategory() {
return category;
}
// Builder class
public static class Builder {
private String title;
private String text;
private String category;
public Builder title(String title) {
this.title = title;
return this;
}
public Builder text(String text) {
this.text = text;
return this;
}
public Builder category(String category) {
this.category = category;
return this;
}
public Post build() {
return new Post(this);
}
}

}

// Usage
public class Main {
public static void main(String[] args) {
Post post = new Post.Builder()
.title(“Java Builder Pattern”)
.text(“Explaining how to implement the Builder Pattern in Java”)
.category(“Design Patterns”)
.build();

    System.out.println("Title: " + post.getTitle());
    System.out.println("Text: " + post.getText());
    System.out.println("Category: " + post.getCategory());
}

}

–Prototype
The Prototype Pattern is a creational design pattern that allows you to create new objects by copying an existing object.
Instead of creating new instances from scratch, you clone an existing prototype, which can be more efficient and flexible. Let’s dive into an example in Java:
Define the Prototype Interface: First, we create an abstract class or interface called Tree with an abstract method copy():
Java

public abstract class Tree {
public abstract Tree copy();
}

Concrete Prototypes: Next, we implement two concrete classes that extend Tree: PlasticTree and PineTree. These classes represent specific types of trees and provide their own implementation of the copy() method:
Java

public class PlasticTree extends Tree {
@Override
public Tree copy() {
PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight());
plasticTreeClone.setPosition(this.getPosition());
return plasticTreeClone;
}
}

public class PineTree extends Tree {
@Override
public Tree copy() {
PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight());
pineTreeClone.setPosition(this.getPosition());
return pineTreeClone;
}
}
In these classes, we customize the cloning process based on the specific tree type. For example, we copy the mass, height, and position of the original tree.
Usage: Now, let’s create instances of our concrete prototypes:
Java

public class Main {
public static void main(String[] args) {
Tree originalPlasticTree = new PlasticTree(10, 5);
Tree clonedPlasticTree = originalPlasticTree.copy();

    Tree originalPineTree = new PineTree(15, 8);
    Tree clonedPineTree = originalPineTree.copy();

    // Use the cloned trees as needed
    // ...
}

}

What are Structural Patterns?
These patterns deal with a class’s structure and composition.

–Adapter Pattern
The Adapter Pattern is a structural design pattern that allows you to make two incompatible interfaces work together.
It acts as a bridge between these interfaces, enabling collaboration without modifying their source code.

Object Adapter:
In this approach, we use composition to delegate logic to the adapter.
The adapter contains the Adaptee and delegates the request() method to the specificRequest() method in the Adaptee.
Java

// Adaptee (existing class)
class Adaptee {
void specificRequest() {
System.out.println(“Adaptee’s specific request”);
}
}

// Target interface
interface Target {
void request();
}

// Adapter (using composition)
class Adapter implements Target {
private final Adaptee adaptee;

Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}

}

Class Adapter:
Requires multiple inheritance (not directly possible in Java).
We can achieve it using interfaces with default methods.
The adapter extends both the Target and the Adaptee classes.
Java

// Target interface
interface Target {
void request();
}

// Adaptee (existing class)
class Adaptee {
void specificRequest() {
System.out.println(“Adaptee’s specific request”);
}
}

// Adapter (using class inheritance)
class Adapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
Benefits and Trade-Offs:
Class Adapter works well for one-to-one method mapping.
Object Adapter provides more flexibility and avoids code modification.

–Decorator Pattern
The Decorator Pattern is a creational design pattern that allows you to dynamically add functionality and behavior to an object without
affecting the behavior of other existing objects within the same class. It’s particularly useful when you want to extend an object’s
capabilities without altering its structure.

Overview:
The Decorator Pattern provides an enhanced interface to the original object by wrapping it with additional functionality.
We prefer composition over inheritance to reduce subclassing overhead.
Decorators can be added at runtime, allowing flexibility.
Example Scenario: Suppose we have a ChristmasTree object, and we want to decorate it with various items like garland, tinsel, and bubble lights. We’ll follow the original Gang of Four design conventions:
Implementation:
First, create a ChristmasTree interface:
Java

public interface ChristmasTree {
String decorate();
}

Implement the interface with a basic tree:
Java

public class ChristmasTreeImpl implements ChristmasTree {
@Override
public String decorate() {
return “Christmas tree”;
}
}

Next, create an abstract TreeDecorator class that implements ChristmasTree and holds the decorated tree:
Java

public abstract class TreeDecorator implements ChristmasTree {
private final ChristmasTree tree;

public TreeDecorator(ChristmasTree tree) {
this.tree = tree;
}
@Override
public String decorate() {
return tree.decorate();
}

}

Now create specific decorators. For example, BubbleLights:
Java

public class BubbleLights extends TreeDecorator {
public BubbleLights(ChristmasTree tree) {
super(tree);
}

@Override
public String decorate() {
return super.decorate() + " with Bubble Lights";
}

}

public class Garland extends TreeDecorator {
public Garland(ChristmasTree tree) {
super(tree);
}

@Override
public String decorate() {
return super.decorate() + " with Garland";
}

}
Usage:
Create decorated trees:
Java

public class DecoratorPatternDemo {
public static void main(String[] args) {
ChristmasTree tree1 = new Garland(new ChristmasTreeImpl());
System.out.println(tree1.decorate()); // Output: “Christmas tree with Garland”

    ChristmasTree tree2 = new BubbleLights(new Garland(new Garland(new ChristmasTreeImpl())));
    System.out.println(tree2.decorate()); // Output: "Christmas tree with Garland with Garland with Bubble Lights"
}

}
The pattern allows adding decorators at runtime, giving flexibility to decorate as needed.
Remember, the Decorator Pattern is widely used in scenarios where you want to enhance an object’s behavior without modifying its core structure

— Facade Pattern
The Facade Pattern is a structural design pattern that simplifies the interaction with a complex subsystem by providing a unified interface.
It acts as a “front door” to hide the underlying complexity and make it easier for clients to use the system

–Composite
The Composite Pattern is a structural design pattern that allows you to compose objects into tree structures to represent part-whole hierarchies.
The main idea behind the Composite Pattern is to build a tree structure of objects, where individual objects and composite objects share a common interface.

Base Component (Department): We’ll define a simple Department interface as our base component:
Java

public interface Department {
void printDepartmentName();
}

Leaf Components (FinancialDepartment and SalesDepartment): Let’s create classes for financial and sales departments, both implementing the Department interface:
Java

public class FinancialDepartment implements Department {
// Implementation details (e.g., id, name)
public void printDepartmentName() {
System.out.println(getClass().getSimpleName());
}
}

public class SalesDepartment implements Department {
// Implementation details (e.g., id, name)
public void printDepartmentName() {
System.out.println(getClass().getSimpleName());
}
}

These leaf classes implement the printDepartmentName() method and don’t contain references to other Department objects.
Composite Element (HeadDepartment): As a composite class, let’s create a HeadDepartment that holds a collection of Department components:
Java

import java.util.ArrayList;
import java.util.List;

public class HeadDepartment implements Department {
private Integer id;
private String name;
private List childDepartments;

public HeadDepartment(Integer id, String name) {
this.id = id;
this.name = name;
this.childDepartments = new ArrayList<>();
}
public void printDepartmentName() {
childDepartments.forEach(Department::printDepartmentName);
}
public void addDepartment(Department department) {
childDepartments.add(department);
}
public void removeDepartment(Department department) {
childDepartments.remove(department);
}

}

The HeadDepartment class implements the printDepartmentName() method and provides methods for adding and removing child departments.
Usage: You can create instances of leaf departments (e.g., FinancialDepartment, SalesDepartment) and a composite department (e.g., HeadDepartment). Add leaf departments to the composite and print their names.
Java

public class Main {
public static void main(String[] args) {
Department finance = new FinancialDepartment();
Department sales = new SalesDepartment();

    HeadDepartment headDepartment = new HeadDepartment(1, "Company Departments");
    headDepartment.addDepartment(finance);
    headDepartment.addDepartment(sales);

    headDepartment.printDepartmentName();
}

}

Output:
FinancialDepartment
SalesDepartment

The Composite Pattern allows you to create flexible tree structures where
individual objects and compositions (composites) can be treated uniformly.

What is HASH Function !!!

A hash function is a mathematical function that takes an input (or “message”) and returns a fixed-size string of characters,
which is typically a sequence of numbers and letters. This output is called a “hash value” or simply a “hash.”
Hash functions are commonly used in computer science and cryptography, and they have a few important properties:

=== How would you use hash function and how it works ?

Deterministic: The same input will always produce the same hash.

Fast: The function can quickly compute the hash value for any given input.
Unique (almost): Two different inputs should produce distinct hashes (this is known as minimizing “collisions”).
Irreversible: Given a hash, it should be computationally infeasible to determine the original input.
Uniform: The hash values are evenly distributed over the output space.

Hash functions are widely used in various applications, such as:

Data Integrity: To ensure that data hasn’t been changed or faked, by comparing hash values.
Password Storage: Securely storing hashed passwords instead of the actual passwords.
Cryptography: Used in digital signatures and other cryptographic algorithms.
Hash Tables: For efficient data retrieval in programming.

=== Give me example of hash map, how would you use it ?

SHA (Secure Hash Algorithm):
Includes variants like SHA-1, SHA-256, SHA-384, and SHA-512.
SHA-256 and SHA-512 are widely used in cryptography and blockchain technology (e.g., Bitcoin).

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashExample {
public static void main(String[] args) {
try {
// The input string to be hashed
String input = “Hello, world!”;

        // Create a MessageDigest instance for SHA-256
        MessageDigest digest = MessageDigest.getInstance("SHA-256");

        // Compute the hash
        byte[] hash = digest.digest(input.getBytes());

        // Convert the hash bytes to a hexadecimal string
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = String.format("%02x", b);
            hexString.append(hex);
        }

        // Print the resulting hash
        System.out.println("SHA-256 Hash: " + hexString.toString());
    } catch (NoSuchAlgorithmException e) {
        System.err.println("Error: " + e.getMessage());
    }
}

}

Input Data: The input variable contains the data you want to hash.
MessageDigest Initialization: The MessageDigest instance is created using getInstance(“SHA-256”), which specifies the SHA-256 hashing algorithm.
Hashing: The digest method computes the hash of the input data.
Hexadecimal Conversion: Since the hash is in binary format, we use a loop to convert it into a more readable hexadecimal string.

Put and Patch ? When to use it ?

Both PUT and PATCH are HTTP methods used to update resources on a server, but they differ in how they handle updates:

PUT:
Full Update:
PUT is used to completely replace an existing resource.
The entire resource needs to be sent in the request, even if only a part of it is being changed.

If the resource doesn’t exist, PUT often creates it (though this behavior may vary).

Idempotent:

Repeated PUT requests with the same data will always result in the same state of the resource.
Example: Updating a user’s profile:

json
PUT /users/123
{
“name”: “Velislav”,
“email”: “velislav@example.com”,
“age”: 30
}
If only the name changes but the email and age remain the same, they must still be included in the request.

PATCH:
Partial Update:

PATCH is used to make a partial update to an existing resource.
Only the fields being changed need to be sent in the request.

Idempotent (Mostly):

Like PUT, repeated PATCH requests with the same data generally result in the same state of the resource.
However, behavior might depend on the server implementation.

Example: Updating only the user’s name:

json
PATCH /users/123
{
“name”: “Velislav”
}
Here, only the name field is updated, leaving other fields unchanged.

Key Difference:
PUT replaces the entire resource, while PATCH modifies only specified fields of the resource.

HTTP methods with a short description.

GET: Used to retrieve data from the server without modifying it. Commonly used for fetching resources or information.
POST: Used to send data to the server, typically to create a new resource. For example, submitting a form or uploading a file.
PUT: Used to update or completely replace an existing resource on the server. If the resource doesn’t exist, it may be created.
PATCH: Used for partial updates to an existing resource, modifying only the specified fields.
DELETE: Used to delete a resource from the server.
HEAD: Similar to GET, used to retrieve headers without the body of a resource.
OPTIONS: It tells what HTTP methods are allowed.
TRACE: Used mainly for diagnostic purposes by echoing the request back to the client
CONNECT: Establishes a tunnel to the server, often used for SSL/TLS connections.

========== Functional Interface ====================================

A functional interface in Java is an interface that contains exactly one abstract method. These interfaces are used primarily in lambda expressions and method references, enabling more concise and readable functional programming.

Key Features:

  1. Single Abstract Method: A functional interface has only one abstract method. This is the essence of its functionality.
  2. Default and Static Methods: Functional interfaces can include default and static methods, but these do not count as the single abstract method.
  3. @FunctionalInterface Annotation: While this annotation is optional, it helps by signaling that the interface is intended to be a functional one. If additional abstract methods are added to the interface, the compiler throws an error.

Example:

@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
// Using lambda expression
public class Main {
public static void main(String[] args) {
// Implementing the calculate method using a lambda
Calculator addition = (a, b) -> a + b;
Calculator multiplication = (a, b) -> a * b;
System.out.println("Addition: " + addition.calculate(5, 3)); // Output: 8
System.out.println("Multiplication: " + multiplication.calculate(5, 3)); // Output: 15
}
}

Use Case:

The Calculator interface in the example has a single abstract method calculate, making it a functional interface.
This allows us to use lambda expressions for brevity and elegance in implementation.

Common Functional Interfaces in Java:

Java provides several built-in functional interfaces in the java.util.function package, such as:

  • Predicate: Takes an input and returns a boolean.
  • Function: Takes an input and returns an output of type R.
  • Supplier: Provides a value without input.
  • Consumer: Accepts a value and performs an action.

Functional interfaces streamline operations, especially in scenarios like working with collections, streams, or asynchronous tasks.

They empower Java to adopt functional programming paradigms while maintaining its object-oriented nature.

================ higher-order function is =============================
A higher-order function is a function that either takes other functions as arguments or returns a function as its result. In other words, it operates on functions, treating them as “first-class citizens.” This is a powerful feature in functional programming paradigms, enabling abstraction, code reuse, and composition.

Example of a Higher-Order Function in Java:

import java.util.function.Function;
public class Main {
public static void main(String[] args) {
// Higher-order function: applies a function to a value
Function<Integer, Integer> squareFunction = x -> x * x;
int result = applyFunction(5, squareFunction);
System.out.println("Square of 5: " + result); // Output: 25
}
public static int applyFunction(int value, Function<Integer, Integer> function) {
return function.apply(value);
}
}

In the example above:

  • The applyFunction method is a higher-order function because it accepts another function (Function<Integer, Integer>) as a parameter.

Characteristics:

  1. Accepts Functions as Arguments: Higher-order functions can take other functions as input to perform specific operations.
  2. Returns Functions: They can generate and return new functions dynamically.
  3. Encourages Code Reuse: Promotes modular and reusable code since functions can be passed around and composed together.

Use Case:

In Java, higher-order functions are commonly used in functional programming with streams, lambdas, and method references. For example:

List<String> names = Arrays.asList("John", "Jane", "Jake");
names.stream()
.filter(name -> name.startsWith("J")) // Takes a lambda function as argument
.forEach(System.out::println); // Takes a method reference as argument

Higher-order functions bring flexibility and power to Java programs, allowing developers to write clean, concise, and expressive code!

Some real world Interview response sets here.

  • Understands basics like polymorphism, static methods, and String pool
  • familiar with collections (e.g., HashSet, TreeSet) and their use cases.
  • Correctly identifies bean scopes (singleton, prototype)
  • Aware of dependency injection and its purpose.
  • knows how to implement many-to-many relationships using junction tables.
  • Understands the purpose of indexes (though superficially)
  • can differentiate between monolithic and microservice architectures
  • understands basic trade-offs (scalability, maintainability)
  • misunderstands Liskov Substitution Principle and Dependency Inversion
  • Struggles to provide concrete examples for Open/Closed Principle.
  • Lacks depth in bean lifecycle management
  • Unclear on advanced dependency injection
  • superficial understanding of indexes (composite indexes, query optimization)
  • Unfamiliar with isolation levels and optimistic/pessimistic locking

Pros:

  • Worked with legacy code in a huge and complex system
  • Knows SOLID
  • Knowledge of streams
  • Explained indexes and ACID
  • Good level of English
    Cons:
  • No responsibility for deployment or infrastructure, participation in architecture design
  • Has some knowledge of the OOP principles but really struggled to explain them and got some concepts wrong
  • General knowledge of collections but has gaps and very uncertain
  • Doesn’t understand the difference between concurrency and parallelism, not very strong knowledge of concurrency
  • Not looked into the micro service architecture much
  • Vague on some database concepts (indexes discussion was surface-level)
  • Couldn’t explain livelock
  • Gaps in finally block understanding
  • Limited knowledge of exception handling specifics
  • Confused around access modifiers (Protected vs. Package Private)
    The consensus opinion …..is
    not suitable for the Mid-Java developer position due to the gaps in his technical knowledge.

Pros
Explained SOLID well
Explained OOP well
Explained composition (but struggled initially a bit)
Excellent understanding of collections
Difference between lists and sets
Hashsets
Hashmap
ConcurrentHashmap
Good understanding of data structures – stacks, queues, trees
Good knowledge of the jvm architecture
Explained concurrency vs parallelism, demonstrated good knowledge of multithreaded applications
Good understanding of REST
Explained db indexes well
Explained different types of joins

Leave a comment