Java notes
Zoltán Szilágyi
2025-04-14
All notes

JAVA basics / core

Core Concepts OOP

1. Core OOP Principles

Encapsulation

  • Wrapping data and methods within a class.
  • Use private variables with public getters and setters.
public class Person {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Abstraction

  • Hiding internal details, exposing only functionality.
  • Achieved using abstract classes and interfaces.
interface Vehicle {
    void start();
}

Inheritance

  • Allows a class to inherit members of another class.
class Animal {
    void makeSound() { System.out.println("Sound"); }
}

class Dog extends Animal {
    void bark() { System.out.println("Bark"); }
}

Polymorphism

  • Multiple forms: Method Overloading and Overriding.

Overloading (compile-time):

int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }

Overriding (runtime):

class Animal {
    void sound() { System.out.println("Animal sound"); }
}

class Cat extends Animal {
    @Override
    void sound() { System.out.println("Meow"); }
}

2. Java-Specific OOP Concepts

  • Access Modifiers: private, protected, public, default
  • Keywords: this, super, final, static, abstract
  • Object Class Methods: equals(), hashCode(), toString(), clone()
  • Constructors: default, parameterized, copy
  • Initialization Blocks: static and instance
  • Interfaces vs Abstract Classes
  • Marker Interfaces: e.g., Serializable
  • Composition vs Inheritance (favor composition)
  • Immutability (create immutable classes)
  • POJOs / JavaBeans
  • Design Patterns: Singleton, Factory, Strategy
  • SOLID Principles
    • Single Responsibility
    • Open/Closed
    • Liskov Substitution
    • Interface Segregation
    • Dependency Inversion

4. Coding Practice Ideas

  • Implement class hierarchy (e.g., Shape → Circle/Rectangle)
  • Override equals() and hashCode() correctly
  • Write a class that demonstrates encapsulation
  • Create an immutable class
  • Design a system (e.g., Library, ATM) using OOP

5. Common Interview Questions

  • What is the difference between an abstract class and an interface?
  • Explain method overloading vs method overriding.
  • How does Java achieve runtime polymorphism?
  • Can you override private/static/final methods?
  • What does the super keyword do?
  • Why is encapsulation important?

pass by reference vs pass by value

Java Collection API

1. Collections Framework Hierarchy

Collection (Interface) ├── List (Interface) │ ├── ArrayList │ ├── LinkedList │ ├── Vector │ └── Stack │ ├── Set (Interface) │ ├── HashSet │ ├── LinkedHashSet │ └── TreeSet │ ├── Queue (Interface) │ ├── PriorityQueue │ └── Deque (Interface) │ └── ArrayDeque │ └── Map (Interface) Not part of Collection interface ├── HashMap ├── LinkedHashMap ├── TreeMap └── Hashtable

2. Core Interfaces and Implementations

List Interface

  • Ordered collection (sequence)
  • Allows duplicates
  • Positional access
ImplementationDescriptionTime Complexity
ArrayListResizable arrayget: O(1), add: O(1) amortized
LinkedListDoubly-linked listget: O(n), add: O(1)
VectorSynchronized ArrayList (legacy)Thread-safe but slower
StackLIFO structure (extends Vector)push/pop: O(1)

Set Interface

  • No duplicates
  • No positional access
ImplementationDescriptionTime Complexity
HashSetHash table implementationadd/contains: O(1)
LinkedHashSetMaintains insertion orderSlightly slower than HashSet
TreeSetRed-Black tree implementationadd/contains: O(log n)

Queue Interface

  • FIFO (First-In-First-Out) typically
  • PriorityQueue is an exception (priority-based)
ImplementationDescriptionTime Complexity
PriorityQueueHeap implementationoffer/poll: O(log n)
ArrayDequeResizable array dequeadd/remove: O(1)

Map Interface

  • Key-value pairs
  • No duplicate keys
ImplementationDescriptionTime Complexity
HashMapHash table implementationget/put: O(1)
LinkedHashMapMaintains insertion/access orderSimilar to HashMap
TreeMapRed-Black tree implementationget/put: O(log n)
HashtableSynchronized (legacy)Thread-safe but slower

3. Key Features and Differences

HashMap vs HashTable

  • HashMap: Not synchronized, allows one null key, faster
  • HashTable: Synchronized, no null keys, legacy class

ArrayList vs LinkedList

  • ArrayList: Better for random access, less memory overhead
  • LinkedList: Better for frequent insertions/deletions, more memory

HashSet vs TreeSet

  • HashSet: Uses hashCode(), O(1) operations, no ordering
  • TreeSet: Uses Comparable/Comparator, O(log n) operations, sorted

4. Important Utility Classes

Collections Class

  • Provides static utility methods:
    • sort(), shuffle(), reverse()
    • synchronizedCollection(), unmodifiableCollection()
    • binarySearch(), frequency(), disjoint()

Arrays Class

  • Utility methods for arrays:
    • asList(), sort(), binarySearch()
    • fill(), copyOf(), equals()

5. Java 8+ Enhancements

Stream API with Collections

List<String> filtered = list.stream()
    .filter(s -> s.startsWith("A"))
    .sorted()
    .collect(Collectors.toList());

New Collection Methods

  • removeIf(), forEach(), computeIfAbsent()
  • merge(), getOrDefault()

Factory Methods (Java 9+)

List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("a", "b");
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);

Thread Safety Considerations

Synchronized Collections

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

Concurrent Collections (java.util.concurrent)

  • ConcurrentHashMap: Thread-safe without full synchronization
  • CopyOnWriteArrayList: Thread-safe List for read-heavy scenarios
  • BlockingQueue: Thread-safe Queue implementations

Common Interview Questions

  • Difference between ArrayList and LinkedList?
  • How does HashMap work internally?
  • What happens when two different keys have same hashCode?
  • How to make a collection immutable?
  • Difference between fail-fast and fail-safe iterators?
  • When to use TreeMap vs HashMap?
  • How to sort a collection of custom objects?
  • What is the contract between equals() and hashCode()?
  • How to implement a LRU cache using LinkedHashMap?
  • Difference between Comparable and Comparator?

Best Practices

  • Prefer interface types in declarations:
List<String> list = new ArrayList<>(); // Good
ArrayList<String> list = new ArrayList<>(); // Avoid
  • Initialize collections with proper capacity when size is known:
new ArrayList<>(100); // Avoids resizing
  • Use immutable collections for thread-safety when possible
  • Choose the right collection based on access patterns
  • Override equals() and hashCode() properly for custom objects in Hash collections

Functional Interfaces and Lambda Expressions in Java

Functional Interfaces

Definition

A functional interface is an interface that contains exactly one abstract method (SAM - Single Abstract Method). They form the basis of lambda expressions in Java.

Key Characteristics

  • Can have any number of default or static methods
  • Annotated with @FunctionalInterface (optional but recommended)
  • Used extensively in Java's Stream API and for lambda expressions

Common Built-in Functional Interfaces

InterfaceMethod SignatureDescription
Predicate<T>boolean test(T t)Tests a condition
Function<T,R>R apply(T t)Transforms input to output
Consumer<T>void accept(T t)Performs operation on input
Supplier<T>T get()Provides a value
UnaryOperator<T>T apply(T t)Special case of Function where input/output types are same
BiFunction<T,U,R>R apply(T t, U u)Takes two inputs, produces output

Creating Custom Functional Interfaces

@FunctionalInterface
interface StringProcessor {
    String process(String input);
    
    default void printInfo() {
        System.out.println("This processes strings");
    }
}

Lambda Expressions

Syntax

(parameters) -> { body }
  • Parentheses optional for single parameter
  • Braces optional for single statement
  • Return statement optional for single expression

Evolution from Anonymous Classes

// Old way (anonymous class)
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Lambda equivalent
Runnable r = () -> System.out.println("Hello");

Examples:

// Predicate
Predicate<Integer> isEven = n -> n % 2 == 0;

// Function
Function<String, Integer> strLength = s -> s.length();

// Consumer
Consumer<String> printer = s -> System.out.println(s);

// Supplier
Supplier<Double> randomSupplier = () -> Math.random();

// Method reference equivalent
Consumer<String> printer = System.out::println;

Common Use Cases

Collections Processing

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
     .filter(name -> name.startsWith("A"))
     .map(String::toUpperCase)
     .forEach(System.out::println);

Thread Initialization

new Thread(() -> System.out.println("Running in thread")).start();

Event Handling

button.addActionListener(e -> System.out.println("Button clicked"));

Java 8+ Enhancements

Default Methods in Interfaces

@FunctionalInterface
interface Greeter {
    void greet(String name);
    
    default void greetDefault() {
        greet("World");
    }
}

Static Methods in Interfaces

@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
    
    static MathOperation add() {
        return (a, b) -> a + b;
    }
}

Best Practices

  • Use @FunctionalInterface annotation for clarity
  • Prefer standard functional interfaces when possible
  • Keep lambdas short - extract complex logic to methods
  • Use method references when they improve readability
  • Avoid side effects in functions - aim for pure functions
  • Consider type inference - don't overspecify parameter types

Common Interview Questions

  • What is a functional interface?
  • Difference between lambda expression and anonymous class?
  • What is the purpose of @FunctionalInterface annotation?
  • Can a functional interface have multiple abstract methods
  • How does variable capture work in lambdas?
  • What are method references and their types?
  • How are lambdas implemented under the hood?
  • What are the performance implications of using lambdas?
  • When would you use Predicate vs Function?
  • How would you create a custom functional interface?

Concurency / Multithreading

Java Concurrency and Multithreading

1. Thread Fundamentals

Creating Threads

// Method 1: Extending Thread class
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}

// Method 2: Implementing Runnable
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable running");
    }
}

// Usage
Thread t1 = new MyThread();
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();

Thread States

  • NEW: Created but not started
  • RUNNABLE: Executing in JVM
  • BLOCKED: Waiting for monitor lock
  • WAITING: Waiting indefinitely
  • TIMED_WAITING: Waiting for specified time
  • TERMINATED: Completed execution

Thread Synchronization

  • Synchronized Methods
public synchronized void increment() {
    count++;
}
  • Synchronized Blocks
public void increment() {
    synchronized(this) {
        count++;
    }
}
  • Volatile Keyword
private volatile boolean running = true;

Java 8+ Enhancements

  • Parallel Streams
List<String> results = dataList.parallelStream()
    .filter(item -> item.startsWith("A"))
    .collect(Collectors.toList());

Common design pattern in Java e.g. Builder, Singleton

1. Creational Patterns

Singleton Pattern

Ensures a class has only one instance and provides global access.

// Thread-safe implementation
public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Builder pattern


@Builder
public class Pizza {
    private String dough;
    private String sauce;
    private String topping;
    
    ....
}

// Usage
Pizza pizza = new Pizza.Builder()
    .dough("thin crust")
    .sauce("tomato")
    .topping("cheese")
    .build();

Factory Method Pattern

  • Creates objects without specifying the exact class.
public interface Vehicle {
    void manufacture();
}

public class Car implements Vehicle {
    public void manufacture() {
        System.out.println("Manufacturing Car");
    }
}

public class Bike implements Vehicle {
    public void manufacture() {
        System.out.println("Manufacturing Bike");
    }
}

public class VehicleFactory {
    public Vehicle getVehicle(String type) {
        if ("car".equalsIgnoreCase(type)) {
            return new Car();
        } else if ("bike".equalsIgnoreCase(type)) {
            return new Bike();
        }
        return null;
    }
}

UML / class / sequence / usecase diagrams

Spring / Spring Boot

Spring Bean

What is a Spring Bean?

A Spring Bean is just an object that is managed by the Spring IoC (Inversion of Control) container. Spring creates and manages the lifecycle and dependencies of these objects for you.

Key Concepts

1. Bean Definition

You declare a bean either through:

  • XML configuration (old-school)
  • Java-based configuration using @Configuration and @Bean
  • Component scanning with annotations like @Component, @Service, @Repository, and @Controller
2. Lifecycle

Spring handles:

  • Bean creation
  • Dependency injection
  • Initialization
  • Destruction (if needed)
3. Scope

By default, Spring Beans are singleton (only one instance per Spring container). But you can change the scope:

  • singleton (default)
  • prototype (new instance every time)
  • request, session, application, etc. (in web contexts)
@Component
@Scope("prototype")
public class MyBean {
    // new instance every time it’s injected
}
4. Dependency Injection (DI)

Spring handles injecting dependencies:

  • Constructor-based: @Autowired on constructor
  • Setter-based: @Autowired on setter method
  • Field-based: @Autowired on field (not always recommended for testability)
5. Bean Configuration Example

Using Java Config:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

Using Component Scanning:

@Component
public class MyServiceImpl implements MyService {
    // Spring will auto-detect and create this bean
}

Lifecycle Hooks

You can hook into bean lifecycle:

  • @PostConstruct: Method to run after bean is created
  • @PreDestroy: Method to run before bean is destroyed
@PostConstruct
public void init() {
    // Initialization logic
}

@PreDestroy
public void cleanup() {
    // Cleanup logic
}

Why Beans Matter

Beans are central to:

  • Loose coupling via DI
  • Configuration management
  • Testability
  • Clean architecture

If you're doing Spring Boot development, it makes heavy use of Spring Beans behind the scenes.

Dependency injection

What is Dependency Injection?

Dependency Injection (DI) is a design pattern where the dependencies (objects a class needs to function) are provided rather than created by the class itself.

In Spring, DI is the cornerstone of its IoC (Inversion of Control) container. It allows for loose coupling and better testability.

Types of Dependency Injection in Spring

1. Constructor Injection

Preferred way for mandatory dependencies.

@Component
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

2. Setter Injection

Used for optional dependencies.

@Component
public class NotificationService {
    private EmailService emailService;

    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

3. Field Injection

Quick and concise, but not ideal for testing.

@Component
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}

Annotations Used in DI

  • @Autowired – Automatically injects dependencies
  • @Qualifier – Used to resolve ambiguity when multiple beans of the same type exist
  • @Inject – From javax.inject, works similarly to @Autowired
  • @Resource – From JSR-250, resolves dependencies by name

Why Use DI?

  • Promotes loose coupling
  • Enhances modularity and testability
  • Makes it easier to manage object creation and lifecycle
  • Encourages clean architecture

Example with Qualifier

@Component
public class MessageService {
    private final Notification notification;

    @Autowired
    public MessageService(@Qualifier("emailNotification") Notification notification) {
        this.notification = notification;
    }
}

Summary

Spring's DI mechanism:

  • Makes code cleaner and easier to manage
  • Helps scale applications
  • Is the foundation for all Spring-based applications

If you're using Spring Boot, DI works out of the box thanks to @SpringBootApplication which triggers component scanning and bean creation automatically.

Spring Controller
What is a Spring Controller?

A Spring Controller is a class annotated with @Controller or @RestController that handles incoming HTTP requests and returns responses. It's part of the Spring MVC framework.

Key Annotations
@Controller

Used in MVC-based web apps. Returns a view name (typically HTML/JSP) instead of the actual response body.

@Controller
public class HomeController {
    @GetMapping("/")
    public String homePage() {
        return "home"; // returns home.jsp or home.html
    }
}
@RestController

Shorthand for @Controller + @ResponseBody. Used in RESTful services. Returns data directly (like JSON or XML).

@RestController
public class ApiController {
    @GetMapping("/api/greet")
    public String greet() {
        return "Hello from API!";
    }
}
Common Request Mapping Annotations
  • @RequestMapping – General-purpose for any HTTP method
  • @GetMapping – For GET requests
  • @PostMapping – For POST requests
  • @PutMapping – For PUT requests
  • @DeleteMapping – For DELETE requests
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // logic to get user
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        // logic to create user
    }
}
Handling Request Parameters
  • @PathVariable – Extract value from URI
  • @RequestParam – Extract query parameters
  • @RequestBody – Deserialize request JSON to Java object
  • @ResponseBody – Serialize Java object to JSON response
@GetMapping("/search")
public List<Product> search(@RequestParam String name) {
    return productService.searchByName(name);
}
Example Controller
@RestController
@RequestMapping("/api")
public class ExampleController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello World!";
    }

    @PostMapping("/echo")
    public String echo(@RequestBody String message) {
        return "Echo: " + message;
    }
}
Spring Boot and Controllers

In Spring Boot:

  • @SpringBootApplication enables component scanning.
  • Place your controllers in the base package or subpackages.
  • REST endpoints are ready to use out of the box.
Summary
  • @Controller for web views, @RestController for REST APIs.
  • Use request mapping annotations to handle HTTP methods.
  • Combine with service and repository layers for clean architecture.
  • Great for building scalable, testable, and maintainable web apps.
Rest API annotations
Spring Profile
RestTemplate
microservices vs REST

Back End Dev Tools

  • Maven (mvn clean install)
  • GitLab and its pipelines
  • IDE
  • Linux

FrontEnd (if applicable)

  • HTML / DOM
  • CSS
  • JavaScript
  • Promises
  • Events
  • React / Angular / TypeScript

Others

  • Agile
  • Production Support
  • Python

Communication Skills

  • Confidence
  • Speaks clearly

Other Tools:

  • flowable platform