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.