Core Java & OOP - Câu hỏi phỏng vấn Senior Engineer

1. Explain the difference between == and equals() method

Câu hỏi:

"Hãy giải thích sự khác biệt giữa toán tử == và phương thức equals() trong Java. Khi nào bạn sử dụng mỗi cái?"

Câu trả lời:

Toán tử ==: - So sánh reference (địa chỉ bộ nhớ) đối với objects - So sánh giá trị đối với primitive types - Không thể override

Phương thức equals(): - So sánh nội dung/giá trị của objects - Có thể override để custom logic - Mặc định trong Object class cũng so sánh reference

Ví dụ minh họa:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Override equals method
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        Person person = (Person) obj;
        return age == person.age && 
               Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// Usage example
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
Person p3 = p1;

System.out.println(p1 == p2);        // false (different objects)
System.out.println(p1 == p3);        // true (same reference)
System.out.println(p1.equals(p2));   // true (same content)
System.out.println(p1.equals(p3));   // true (same content)

// String examples
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
String s4 = "hello";

System.out.println(s1 == s2);        // false
System.out.println(s1.equals(s2));   // true
System.out.println(s3 == s4);        // true (string pool)

2. Explain Java Memory Model and Garbage Collection

Câu hỏi:

"Hãy giải thích Java Memory Model và các loại Garbage Collector. Bạn sẽ chọn GC nào cho một high-throughput application?"

Câu trả lời:

Java Memory Model: - Heap Memory: Lưu objects, chia thành Young Generation và Old Generation - Stack Memory: Lưu method calls, local variables, partial results - Method Area: Lưu class metadata, constants, static variables - PC Register: Program counter cho thread hiện tại - Native Method Stack: Cho native method calls

Garbage Collectors:

Ví dụ minh họa:

public class MemoryExample {
    private static final int MB = 1024 * 1024;
    private static List<byte[]> memoryLeak = new ArrayList<>();

    public static void analyzeMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();

        System.out.println("=== Memory Information ===");
        System.out.println("Max memory: " + runtime.maxMemory() / MB + " MB");
        System.out.println("Total memory: " + runtime.totalMemory() / MB + " MB");
        System.out.println("Free memory: " + runtime.freeMemory() / MB + " MB");
        System.out.println("Used memory: " + 
            (runtime.totalMemory() - runtime.freeMemory()) / MB + " MB");
    }

    public static void demonstrateGC() {
        System.out.println("Before allocation:");
        analyzeMemoryUsage();

        // Allocate large objects
        List<LargeObject> objects = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            objects.add(new LargeObject());
        }

        System.out.println("\nAfter allocation:");
        analyzeMemoryUsage();

        // Clear references
        objects.clear();
        objects = null;

        // Suggest GC
        System.gc();

        System.out.println("\nAfter GC:");
        analyzeMemoryUsage();
    }

    static class LargeObject {
        private byte[] data = new byte[1024 * 100]; // 100KB
    }
}

// JVM tuning examples
// -Xms2g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
// -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

3. Multithreading and Concurrency

Câu hỏi:

"Hãy giải thích về thread safety và implement một thread-safe singleton pattern. Sự khác biệt giữa synchronized, volatile, và atomic variables là gì?"

Câu trả lời:

Thread Safety có nghĩa là code có thể được thực thi correctly trong môi trường multithreaded mà không gây ra data corruption hay inconsistent state.

Ví dụ Thread-Safe Singleton:

// Double-checked locking singleton
public class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;
    private static final Object lock = new Object();

    private ThreadSafeSingleton() {
        // Private constructor
    }

    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }

    // Better approach: Enum Singleton
    public enum EnumSingleton {
        INSTANCE;

        public void doSomething() {
            System.out.println("Doing something...");
        }
    }
}

// Comparison of synchronization mechanisms
public class ConcurrencyExample {
    private int normalCounter = 0;
    private volatile int volatileCounter = 0;
    private AtomicInteger atomicCounter = new AtomicInteger(0);
    private final Object lock = new Object();

    // Synchronized method
    public synchronized void incrementNormalSync() {
        normalCounter++;
    }

    // Synchronized block
    public void incrementNormalBlock() {
        synchronized (lock) {
            normalCounter++;
        }
    }

    // Volatile (not thread-safe for increment)
    public void incrementVolatile() {
        volatileCounter++; // NOT thread-safe!
    }

    // Atomic operation
    public void incrementAtomic() {
        atomicCounter.incrementAndGet(); // Thread-safe
    }

    // Demonstrate race condition
    public static void demonstrateRaceCondition() throws InterruptedException {
        ConcurrencyExample example = new ConcurrencyExample();
        ExecutorService executor = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(1000);

        // Start 1000 threads
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                example.incrementNormalSync();
                example.incrementAtomic();
                latch.countDown();
            });
        }

        latch.await();
        executor.shutdown();

        System.out.println("Normal counter: " + example.normalCounter);
        System.out.println("Atomic counter: " + example.atomicCounter.get());
    }
}

4. Collections Framework Deep Dive

Câu hỏi:

"So sánh ArrayList vs LinkedList, HashMap vs ConcurrentHashMap. Khi nào bạn sử dụng mỗi loại?"

Câu trả lời:

Ví dụ Performance Comparison:

public class CollectionsPerformanceTest {
    private static final int SIZE = 100_000;

    public static void compareListPerformance() {
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();

        // Test insertion at end
        long start = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            arrayList.add(i);
        }
        long arrayListInsertTime = System.nanoTime() - start;

        start = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            linkedList.add(i);
        }
        long linkedListInsertTime = System.nanoTime() - start;

        // Test random access
        start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            arrayList.get(SIZE / 2);
        }
        long arrayListAccessTime = System.nanoTime() - start;

        start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            linkedList.get(SIZE / 2);
        }
        long linkedListAccessTime = System.nanoTime() - start;

        System.out.println("ArrayList insert: " + arrayListInsertTime + " ns");
        System.out.println("LinkedList insert: " + linkedListInsertTime + " ns");
        System.out.println("ArrayList access: " + arrayListAccessTime + " ns");
        System.out.println("LinkedList access: " + linkedListAccessTime + " ns");
    }

    public static void compareMapPerformance() throws InterruptedException {
        Map<String, Integer> hashMap = new HashMap<>();
        Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

        // Concurrent modification test
        ExecutorService executor = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(100);

        // This would throw ConcurrentModificationException with HashMap
        // but works fine with ConcurrentHashMap
        for (int i = 0; i < 100; i++) {
            final int index = i;
            executor.submit(() -> {
                try {
                    concurrentHashMap.put("key" + index, index);
                    concurrentHashMap.get("key" + (index - 1));
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executor.shutdown();

        System.out.println("ConcurrentHashMap size: " + concurrentHashMap.size());
    }
}

5. Exception Handling Best Practices

Câu hỏi:

"Hãy giải thích hierarchy của Exception trong Java và best practices cho exception handling trong enterprise applications."

Câu trả lời:

Exception Hierarchy: - Throwable → Error (system errors) - Throwable → Exception → RuntimeException (unchecked) - Throwable → Exception → Other exceptions (checked)

Ví dụ Best Practices:

// Custom exception hierarchy
public abstract class BusinessException extends Exception {
    private final String errorCode;

    public BusinessException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public BusinessException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

public class UserNotFoundException extends BusinessException {
    public UserNotFoundException(Long userId) {
        super("USER_NOT_FOUND", "User with ID " + userId + " not found");
    }
}

public class InvalidEmailException extends BusinessException {
    public InvalidEmailException(String email) {
        super("INVALID_EMAIL", "Email format is invalid: " + email);
    }
}

// Service layer with proper exception handling
@Service
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    private UserRepository userRepository;

    public User findUserById(Long userId) throws UserNotFoundException {
        try {
            return userRepository.findById(userId)
                    .orElseThrow(() -> new UserNotFoundException(userId));
        } catch (DataAccessException ex) {
            logger.error("Database error while finding user: {}", userId, ex);
            throw new RuntimeException("Database error occurred", ex);
        }
    }

    public User createUser(CreateUserRequest request) throws InvalidEmailException {
        try {
            validateEmail(request.getEmail());

            User user = new User();
            user.setEmail(request.getEmail());
            user.setName(request.getName());

            return userRepository.save(user);

        } catch (InvalidEmailException ex) {
            logger.warn("Invalid email provided: {}", request.getEmail());
            throw ex; // Re-throw business exception
        } catch (Exception ex) {
            logger.error("Unexpected error creating user", ex);
            throw new RuntimeException("Failed to create user", ex);
        }
    }

    private void validateEmail(String email) throws InvalidEmailException {
        if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
            throw new InvalidEmailException(email);
        }
    }
}

// Global exception handler
@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(ex.getErrorCode(), ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(InvalidEmailException.class)
    public ResponseEntity<ErrorResponse> handleInvalidEmail(InvalidEmailException ex) {
        ErrorResponse error = new ErrorResponse(ex.getErrorCode(), ex.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
        logger.error("Unexpected error occurred", ex);
        ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

6. Functional Programming in Java 8+

Câu hỏi:

"Hãy giải thích về Stream API và Lambda expressions. Implement một complex data processing pipeline sử dụng functional programming."

Câu trả lời:

Ví dụ Complex Stream Processing:

public class StreamExample {

    public static class Transaction {
        private String id;
        private BigDecimal amount;
        private LocalDateTime timestamp;
        private String category;
        private String userId;

        // Constructor, getters, setters...
    }

    public class TransactionAnalyzer {

        public Map<String, BigDecimal> calculateTopSpendingCategories(
                List<Transaction> transactions, int topN) {

            return transactions.stream()
                    .filter(t -> t.getAmount().compareTo(BigDecimal.ZERO) > 0)
                    .collect(Collectors.groupingBy(
                            Transaction::getCategory,
                            Collectors.reducing(BigDecimal.ZERO, 
                                    Transaction::getAmount, 
                                    BigDecimal::add)))
                    .entrySet().stream()
                    .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
                    .limit(topN)
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            (e1, e2) -> e1,
                            LinkedHashMap::new));
        }

        public Optional<Transaction> findLargestTransaction(List<Transaction> transactions) {
            return transactions.stream()
                    .max(Comparator.comparing(Transaction::getAmount));
        }

        public Map<String, List<Transaction>> groupByUserAndSort(
                List<Transaction> transactions) {

            return transactions.stream()
                    .collect(Collectors.groupingBy(
                            Transaction::getUserId,
                            Collectors.collectingAndThen(
                                    Collectors.toList(),
                                    list -> list.stream()
                                            .sorted(Comparator.comparing(Transaction::getTimestamp))
                                            .collect(Collectors.toList()))));
        }

        // Custom collector example
        public BigDecimal calculateAverageExcludingOutliers(List<Transaction> transactions) {
            return transactions.stream()
                    .map(Transaction::getAmount)
                    .collect(new OutlierExcludingAverageCollector());
        }
    }

    // Custom Collector implementation
    public static class OutlierExcludingAverageCollector 
            implements Collector<BigDecimal, List<BigDecimal>, BigDecimal> {

        @Override
        public Supplier<List<BigDecimal>> supplier() {
            return ArrayList::new;
        }

        @Override
        public BiConsumer<List<BigDecimal>, BigDecimal> accumulator() {
            return List::add;
        }

        @Override
        public BinaryOperator<List<BigDecimal>> combiner() {
            return (list1, list2) -> {
                list1.addAll(list2);
                return list1;
            };
        }

        @Override
        public Function<List<BigDecimal>, BigDecimal> finisher() {
            return amounts -> {
                if (amounts.isEmpty()) return BigDecimal.ZERO;

                // Remove outliers (values beyond 2 standard deviations)
                BigDecimal mean = amounts.stream()
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
                        .divide(BigDecimal.valueOf(amounts.size()), RoundingMode.HALF_UP);

                // Calculate standard deviation and filter
                return amounts.stream()
                        .filter(amount -> isWithinRange(amount, mean, amounts))
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
                        .divide(BigDecimal.valueOf(amounts.size()), RoundingMode.HALF_UP);
            };
        }

        @Override
        public Set<Characteristics> characteristics() {
            return Set.of();
        }

        private boolean isWithinRange(BigDecimal amount, BigDecimal mean, List<BigDecimal> amounts) {
            // Simplified outlier detection logic
            return true;
        }
    }
}

Các câu hỏi này đều yêu cầu hiểu biết sâu về Java và khả năng áp dụng vào các tình huống thực tế trong enterprise applications.