CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Trang chủHướng dẫnCác câu hỏi phỏng vấn lập trình Java (Kèm đáp án)
Java

Các câu hỏi phỏng vấn lập trình Java (Kèm đáp án)

CyStack blog 25 phút để đọc
CyStack blog26/07/2025
Locker Avatar

Bao Tran

Web Developer

Locker logo social
Reading Time: 25 minutes

Bạn đang tìm kiếm tài liệu tham khảo đáng tin cậy để ôn luyện trước buổi phỏng vấn Java của mình? Vậy hãy cùng khám phá bộ câu hỏi này nhé.

câu hỏi phỏng vấn lập trình Java

Chúng tôi đã tổng hợp những vấn đề quan trọng nhất, từ các nền tảng cơ bản bạn cần biết cho đến những chủ đề nâng cao hơn. Các câu hỏi phỏng vấn lập trình Java này sẽ giúp bạn sẵn sàng cho bất kỳ thử thách nào có thể xuất hiện ở buổi phỏng vấn sắp tới.

Câu hỏi phỏng vấn lập trình Java

Làm thế nào để đảo ngược một chuỗi trong Java?

Class String không có sẵn phương thức reverse(). Tuy nhiên, ta có thể tạo một mảng ký tự từ chuỗi rồi duyệt ngược mảng này. Sau đó, ta có thể nối (append) các ký tự vào một đối tượng StringBuilder và cuối cùng trả về chuỗi đã được đảo ngược.

Đây là code mẫu diễn tả thuật toán trên:

public class StringPrograms {

 public static void main(String[] args) {
  String str = "123";

  System.out.println(reverse(str));
 }

 public static String reverse(String in) {
  if (in == null)
   throw new IllegalArgumentException("Null is not valid input");

  StringBuilder out = new StringBuilder();

  char[] chars = in.toCharArray();

  for (int i = chars.length - 1; i >= 0; i--)
   out.append(chars[i]);

  return out.toString();
 }

}

Một điểm cần lưu ý là ta nên thêm bước kiểm tra null như phương thức trên và sử dụng StringBuilder để nối các ký tự. Ngoài ra, index (chỉ số) trong Java bắt đầu từ 0, vì vậy vòng lặp for cần bắt đầu từ length() - 1.

Làm thế nào để hoán đổi hai số mà không cần dùng biến thứ ba trong Java?

Việc hoán đổi hai số mà không cần dùng đến biến thứ ba là một quy trình gồm ba bước. Để dễ hình dung hơn, hãy xem qua diễn giải dưới đây:

b = b + a; // now b is sum of both the numbers
a = b - a; // b - a = (b + a) - a = b (a is swapped)
b = b - a; // (b + a) - b = a (b is swapped)

Còn đây là code triển khai cách làm trên:

public class SwapNumbers {

public static void main(String[] args) {
 int a = 10;
 int b = 20;

    System.out.println("a is " + a + " and b is " + b);

 a = a + b;
 b = a - b;
 a = a - b;

    System.out.println("After swapping, a is " + a + " and b is " + b);
    }

}

Kết quả output cho thấy các giá trị số nguyên đã được hoán đổi:

Outputa is 10 and b is 20
After swapping, a is 20 and b is 10

Viết chương trình Java để kiểm tra một chuỗi có chứa nguyên âm hay không

Ví dụ sau cho thấy cách sử dụng một biểu thức chính quy (regular expression) để kiểm tra xem chuỗi có chứa nguyên âm không:

public class StringContainsVowels {

 public static void main(String[] args) {
  System.out.println(stringContainsVowels("Hello")); // true
  System.out.println(stringContainsVowels("TV")); // false
 }

 public static boolean stringContainsVowels(String input) {
  return input.toLowerCase().matches(".*[aeiou].*");
 }

}

Viết chương trình Java để kiểm tra một số có phải là số nguyên tố không

Ta có thể viết một chương trình để chia lấy số đã cho (n) lần lượt cho các số từ 2 đến n/2 và kiểm tra số dư. Nếu có bất kì số dư nào bằng 0, thì đó không phải là số nguyên tố.

Đoạn code sau đây triển khai thuật toán trên:

public class PrimeNumberCheck {

 public static void main(String[] args) {
  System.out.println(isPrime(19)); // true
  System.out.println(isPrime(49)); // false
 }

 public static boolean isPrime(int n) {
  if (n == 0 || n == 1) {
   return false;
  }
  if (n == 2) {
   return true;
  }
  for (int i = 2; i <= n / 2; i++) {
   if (n % i == 0) {
    return false;
   }
  }

  return true;
 }

}

Mặc dù chương trình này chạy ra kết quả đúng, nó không thực sự hiệu quả về mặt bộ nhớ và thời gian. Hãy nhớ rằng, với một số N cho trước, nếu có một số nguyên tố p nằm trong khoảng từ √N đến N (căn bậc hai của N) mà N chia hết cho nó, thì N không phải là số nguyên tố.

Viết chương trình Java để in ra dãy Fibonacci bằng đệ quy

Dãy Fibonacci là một dãy số trong đó mỗi số luôn là tổng của hai số đứng trước nó. Trong ví dụ này, dãy số bắt đầu bằng 0 và 1. Đoạn code ví dụ sau cho thấy cách sử dụng vòng lặp for để in ra dãy Fibonacci:

public class PrintFibonacci {

 public static void printFibonacciSequence(int count) {
  int a = 0;
  int b = 1;
  int c = 1;

  for (int i = 1; i <= count; i++) {
   System.out.print(a + ", ");

            a = b;
   b = c;
   c = a + b;
  }
 }

 public static void main(String[] args) {
     printFibonacciSequence(10);
 }

}
Output0, 1, 1, 2, 3, 5, 8, 13, 21, 34,

Ta cũng có thể sử dụng đệ quy để in ra dãy Fibonacci, vì bản chất của một số Fibonacci là được tạo ra bằng cách cộng hai số liền trước nó trong dãy:

F(N) = F(N-1) + F(N-2)

Class ở ví dụ dưới sử dụng tinh đệ quy này để tính một dãy Fibonacci có 10 số:

public class PrintFibonacciRecursive {

    public static int fibonacci(int count) {
  if (count <= 1)
   return count;

  return fibonacci(count - 1) + fibonacci(count - 2);
 }

 public static void main(String args[]) {
     int seqLength = 10;

     System.out.print("A Fibonacci sequence of " + seqLength + " numbers: ");

     for (int i = 0; i < seqLength; i++) {
           System.out.print(fibonacci(i) + " ");
     }
   }

}
OutputA Fibonacci sequence of 10 numbers: 0 1 1 2 3 5 8 13 21 34

Làm thế nào để kiểm tra một danh sách số nguyên chỉ chứa số lẻ trong Java?

Ta có thể dùng vòng lặp for và kiểm tra xem mỗi phần tử có phải là số lẻ không:

public static boolean onlyOddNumbers(List<Integer> list) {
 for (int i : list) {
  if (i % 2 == 0)
   return false;
 }

 return true;
}

Nếu danh sách số quá lớn, ta có thể sử dụng parallel stream để xử lý nhanh hơn, như trong ví dụ sau:

public static boolean onlyOddNumbers(List<Integer> list) {
 return list
   .parallelStream() // parallel stream for faster processing
   .anyMatch(x -> x % 2 != 0); // return as soon as any elements match the condition
}

Để tìm hiểu thêm về phép toán dùng để xác định một số nguyên có phải là số lẻ hay không, hãy tham khảo bài viết về phép toán Modulo trên Wikipedia.

Làm thế nào để kiểm tra một chuỗi có phải là palindrome trong Java?

Một chuỗi là palindrome khi nó đọc xuôi hay ngược đều giống nhau. Để kiểm tra tính palindrome, ta có thể đảo ngược chuỗi đầu vào và kiểm tra xem kết quả có bằng với chuỗi đầu vào hay không.

Đoạn code ví dụ sau cho thấy cách sử dụng phương thức StringBuilder.reverse() để kiểm tra chuỗi palindrome:

boolean checkPalindromeString(String input) {
 boolean result = true;
 int length = input.length();

 for (int i = 0; i < length/2; i++) {
  if (input.charAt(i) != input.charAt(length - i - 1)) {
   result = false;
   break;
  }
 }

 return result;
}

Làm thế nào để xóa các khoảng trắng trong một chuỗi ở Java?

Đoạn code ví dụ sau cho thấy một giải pháp xóa các khoảng trắng khỏi chuỗi bằng phương thức replaceAll():

String removeWhiteSpaces(String input) {
 StringBuilder output = new StringBuilder();
 
 char[] charArray = input.toCharArray();
 
 for (char c : charArray) {
  if (!Character.isWhitespace(c))
   output.append(c);
 }
 
 return output.toString();
}

Làm thế nào để xóa các khoảng trắng ở đầu và cuối chuỗi trong Java?

Class String chứa hai phương thức để xóa khoảng trắng ở đầu và cuối: trim()strip().

Phương thức strip() được thêm vào class String trong Java 11. Nó sử dụng phương thức Character.isWhitespace() để kiểm tra xem ký tự có phải là khoảng trắng hay không. Phương thức này sử dụng các điểm mã Unicode, trong khi phương thức trim() xác định bất kỳ ký tự nào có giá trị điểm mã nhỏ hơn hoặc bằng U+0020 là một ký tự khoảng trắng.

Ta nên dùng strip() vì nó sử dụng tiêu chuẩn Unicode. Đoạn code ví dụ sau cho thấy cách sử dụng phương thức này để xóa khoảng trắng:

String s = "  abc  def\\t";
  
s = s.strip();
  
System.out.println(s);

Bởi vì String là đối tượng bất biến (immutable), ta phải gán kết quả của strip() cho một chuỗi khác.

Làm thế nào để sắp xếp một mảng trong Java?

Lớp tiện ích Arrays có nhiều phương thức sort() được nạp chồng (overload) để sắp xếp các mảng kiểu nguyên thủy (primitive) và đối tượng. Nếu bạn đang sắp xếp một mảng nguyên thủy theo thứ tự tự nhiên, bạn có thể sử dụng phương thức Arrays.sort() như trong ví dụ sau:

int[] array = {1, 2, 3, -1, -2, 4};

Arrays.sort(array);

System.out.println(Arrays.toString(array));

Tuy nhiên, nếu muốn sắp xếp một mảng các đối tượng, thì đối tượng đó phải triển khai interface Comparable. Nếu muốn chỉ định tiêu chí sắp xếp, ta có thể truyền vào một Comparator để chỉ định logic sắp xếp.

Làm thế nào để chủ định lập trình ra một trường hợp deadlock trong Java?

Deadlock là một tình huống trong môi trường Java đa luồng khi hai hay nhiều thread bị khóa (block) và chờ nhau mãi mãi. Tình huống deadlock này phát sinh khi có từ hai thread trở lên.

Đoạn code ví dụ sau sẽ tạo ra một deadlock:

public class ThreadDeadlock {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
    
        Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
        Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
        Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
        
        t1.start();
        Thread.sleep(5000);
        t2.start();
        Thread.sleep(5000);
        t3.start();        
    }

}

class SyncThread implements Runnable {

    private Object obj1;
    private Object obj2;

    public SyncThread(Object o1, Object o2) {
        this.obj1 = o1;
        this.obj2 = o2;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();

        System.out.println(name + " acquiring lock on " + obj1);
        synchronized (obj1) {
            System.out.println(name + " acquired lock on " + obj1);
            work();
            System.out.println(name + " acquiring lock on " + obj2);
            synchronized (obj2) {
                System.out.println(name + " acquired lock on " + obj2);
                work();
            }
            System.out.println(name + " released lock on " + obj2);
        }
        System.out.println(name + " released lock on " + obj1);
        System.out.println(name + " finished execution.");
    }

    private void work() {
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

Cả ba thread sẽ có thể chiếm được lock trên đối tượng đầu tiên. Tuy nhiên, chúng đang sử dụng tài nguyên chia sẻ và được khởi động theo cách khiến chúng phải chờ đợi vô thời hạn để chiếm được lock trên đối tượng thứ hai. Ta có thể sử dụng thread dump (thông tin trạng thái, stack trace của tất cả thread) của Java để phát hiện các deadlock.

Làm thế nào để tìm giai thừa của một số nguyên trong Java?

Giai thừa của một số nguyên được tính bằng cách nhân tất cả các số từ 1 đến số đã cho:

F(n) = F(1)*F(2)...F(n-1)*F(n)

Đoạn code sau sử dụng đệ quy để tìm giai thừa của một số nguyên:

public static long factorial(long n) {
 if (n == 1)
  return 1;
 else
  return (n * factorial(n - 1));
}

Làm thế nào để đảo ngược một linked list (danh sách liên kết) trong Java?

LinkedList.descendingIterator() trả về một iterator để duyệt qua các phần tử theo thứ tự ngược lại. Đoạn code sử dụng iterator này để tạo một Linked List mới với các phần tử được liệt kê theo thứ tự đảo ngược:

LinkedList<Integer> ll = new LinkedList<>();

ll.add(1);
ll.add(2);
ll.add(3);

System.out.println(ll);

LinkedList<Integer> ll1 = new LinkedList<>();

ll.descendingIterator().forEachRemaining(ll1::add);

System.out.println(ll1);

Làm thế nào để triển khai thuật toán tìm kiếm nhị phân (binary search) trong Java?

Các phần tử của một mảng phải được sắp xếp trước để có thể triển khai tìm kiếm nhị phân trên đó. Thuật toán tìm kiếm nhị phân dựa trên các điều kiện sau:

  • Nếu khóa (key) nhỏ hơn phần tử ở giữa, thì bây giờ ta chỉ cần tìm kiếm trong nửa đầu của mảng.
  • Nếu khóa lớn hơn phần tử ở giữa, thì ta chỉ cần tìm kiếm trong nửa sau của mảng.
  • Nếu khóa bằng với phần tử ở giữa trong mảng, thì việc tìm kiếm kết thúc.
  • Cuối cùng, nếu không tìm thấy khóa trong toàn bộ mảng, thì nó sẽ trả về -1. Điều này cho biết phần tử đó không tồn tại trong mảng.

Đoạn code ví dụ sau triển khai thuật toán tìm kiếm nhị phân trên:

public static int binarySearch(int arr[], int low, int high, int key) {
 int mid = (low + high) / 2;

 while (low <= high) {
  if (arr[mid] < key) {
   low = mid + 1;
  } else if (arr[mid] == key) {
   return mid;
  } else {
   high = mid - 1;
  }
  mid = (low + high) / 2;
 }

 if (low > high) {
  return -1;
 }

 return -1;
}

Viết chương trình Java minh họa thuật toán sắp xếp trộn (merge sort)

Sắp xếp trộn là một trong những thuật toán sắp xếp có hiệu quả cao nhất. Nó hoạt động dựa trên nguyên tắc “chia để trị”. Thuật toán này chia một danh sách thành nhiều danh sách con cho đến khi mỗi danh sách con chỉ bao gồm một phần tử, sau đó hợp nhất các danh sách con đó để cho ra một danh sách đã được sắp xếp.

Đoạn code sau là một ví dụ của thuật toán sắp xếp trộn:

public class MergeSort {

 public static void main(String[] args) {
  int[] arr = { 70, 50, 30, 10, 20, 40, 60 };

  int[] merged = mergeSort(arr, 0, arr.length - 1);

  for (int val : merged) {
   System.out.print(val + " ");
  }
 }

 public static int[] mergeTwoSortedArrays(int[] one, int[] two) {
  int[] sorted = new int[one.length + two.length];

  int i = 0;
  int j = 0;
  int k = 0;

  while (i < one.length && j < two.length) {
   if (one[i] < two[j]) {
    sorted[k] = one[i];
    k++;
    i++;
   } else {
    sorted[k] = two[j];
    k++;
    j++;
   }
  }

  if (i == one.length) {
   while (j < two.length) {
    sorted[k] = two[j];
    k++;
    j++;
   }
  }

  if (j == two.length) {
   while (i < one.length) {
    sorted[k] = one[i];
    k++;
    i++;
   }
  }

  return sorted;
 }

 public static int[] mergeSort(int[] arr, int lo, int hi) {
  if (lo == hi) {
   int[] br = new int[1];
   br[0] = arr[lo];

   return br;
  }

  int mid = (lo + hi) / 2;

  int[] fh = mergeSort(arr, lo, mid);
  int[] sh = mergeSort(arr, mid + 1, hi);

  int[] merged = mergeTwoSortedArrays(fh, sh);

  return merged;
 }

}

Ta có thể tạo ra một kim tự tháp ký tự trong Java không?

Các bài toán về vẽ mẫu hình (pattern) là một chủ đề rất phổ biến khi phỏng vấn. Loại câu hỏi này được dùng để thử khả năng tư duy logic của ứng viên. Hãy tham khảo bài về chương trình vẽ mẫu hình kim tự tháp trong Java của chúng tôi để xem những cách giải quyết khác nhau cho vấn đề này.

Viết chương trình Java kiểm tra xem hai mảng có chứa các phần tử giống nhau không

Trước tiên ta cần tạo một tập hợp (set) các phần tử từ cả hai mảng, sau đó so sánh các phần tử trong hai tập hợp này để tìm xem có phần tử nào không tồn tại cùng lúc ở cả hai tập hợp hay không.

Đoạn code sau cho thấy cách kiểm tra xem hai mảng có chỉ chứa các phần tử chung hay không:

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class ArraySameElements {

 public static void main(String[] args) {
  Integer[] a1 = {1,2,3,2,1};
  Integer[] a2 = {1,2,3};
  Integer[] a3 = {1,2,3,4};
  
  System.out.println(sameElements(a1, a2));
  System.out.println(sameElements(a1, a3));
 }

 static boolean sameElements(Object[] array1, Object[] array2) {
  Set<Object> uniqueElements1 = new HashSet<>(Arrays.asList(array1));
  Set<Object> uniqueElements2 = new HashSet<>(Arrays.asList(array2));
  
  // if size is different, means there will be a mismatch
  if (uniqueElements1.size() != uniqueElements2.size()) return false;
  
  for (Object obj : uniqueElements1) {
   // element not present in both?
   if (!uniqueElements2.contains(obj)) return false;
  }
  
  return true;
 }

}
Outputtrue
false

Làm thế nào để tính tổng tất cả các phần tử trong một mảng số nguyên ở Java?

Ta có thể sử dụng vòng lặp for để duyệt qua các phần tử của mảng và cộng chúng lại để có được tổng cuối cùng:

int[] array = { 1, 2, 3, 4, 5 };

int sum = 0;

for (int i : array)
 sum += i;

System.out.println(sum);

Làm thế nào để tìm số lớn nhì trong một mảng ở Java?

Có nhiều cách để giải quyết vấn đề này. Ta có thể sắp xếp mảng theo thứ tự tăng dần tự nhiên và lấy giá trị áp chót. Tuy nhiên, sắp xếp là một thao tác tốn nhiều tài nguyên.

Có một cách khác là sử dụng hai biến để tìm giá trị lớn thứ hai chỉ trong một lần duyệt qua mảng như trong ví dụ sau:

private static int findSecondHighest(int[] array) {
 int highest = Integer.MIN_VALUE;
 int secondHighest = Integer.MIN_VALUE;

 for (int i : array) {
  if (i > highest) {
   secondHighest = highest;
   highest = i;
  } else if (i > secondHighest) {
   secondHighest = i;
  }

 }
 return secondHighest;
}

Làm thế nào để xáo một mảng trong Java?

Đoạn code ví dụ sau cho thấy cách sử dụng lớp Random để tạo ra các chỉ số ngẫu nhiên và xáo trộn các phần tử:

int[] array = { 1, 2, 3, 4, 5, 6, 7 };

Random rand = new Random();

for (int i = 0; i < array.length; i++) {
 int randomIndexToSwap = rand.nextInt(array.length);
 int temp = array[randomIndexToSwap];
 array[randomIndexToSwap] = array[i];
 array[i] = temp;
}

System.out.println(Arrays.toString(array));

Ta có thể chạy đoạn code xáo trộn bên trong một vòng lặp for khác để xáo trộn thêm nhiều vòng.

Làm thế nào để tìm một chuỗi trong một file văn bản ở Java?

Đoạn code sau sử dụng lớp Scanner để đọc nội dung file tuần tự từng dòng và sau đó sử dụng phương thức String.contains() để kiểm tra xem chuỗi có tồn tại trong file không:

boolean findStringInFile(String filePath, String str) throws FileNotFoundException {
 File file = new File(filePath);

 Scanner scanner = new Scanner(file);

 // read the file line by line
 while (scanner.hasNextLine()) {
  String line = scanner.nextLine();
  if (line.contains(str)) {
   scanner.close();
   return true;
  }
 }
 scanner.close();

 return false;
}

Lưu ý rằng đoạn code trên giả định rằng chuỗi mà bạn đang tìm kiếm trong file không chứa ký tự xuống dòng.

Làm thế nào để in ngày tháng theo một định dạng cụ thể trong Java?

Đoạn code ví dụ sau sử dụng lớp SimpleDateFormat để định dạng chuỗi ngày tháng theo ý ta muốn:

String pattern = "MM-dd-yyyy";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);

String date = simpleDateFormat.format(new Date());
System.out.println(date); // 06-23-2020

Làm thế nào để gộp hai list trong Java?

Ví dụ ở dưới sử dụng phương thức addAll() để gộp nhiều list trong Java:

List<String> list1 = new ArrayList<>();
list1.add("1");
List<String> list2 = new ArrayList<>();
list2.add("2");

List<String> mergedList = new ArrayList<>(list1);
mergedList.addAll(list2);
System.out.println(mergedList); // [1, 2]

Viết chương trình Java sắp xếp HashMap theo giá trị

HashMap không phải là một collection có thứ tự. Đoạn code sau sắp xếp các entry dựa trên giá trị và lưu chúng vào LinkedHashMap (vốn duy trì thứ tự chèn vào của phần tử):

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class SortHashMapByValue {

 public static void main(String[] args) {
  Map<String, Integer> scores = new HashMap<>();

  scores.put("David", 95);
  scores.put("Jane", 80);
  scores.put("Mary", 97);
  scores.put("Lisa", 78);
  scores.put("Dino", 65);

  System.out.println(scores);

  scores = sortByValue(scores);

  System.out.println(scores);
 }

 private static Map<String, Integer> sortByValue(Map<String, Integer> scores) {
  Map<String, Integer> sortedByValue = new LinkedHashMap<>();

  // get the entry set
  Set<Entry<String, Integer>> entrySet = scores.entrySet();
  System.out.println(entrySet);

  // create a list since the set is unordered
  List<Entry<String, Integer>> entryList = new ArrayList<>(entrySet);
  System.out.println(entryList);

  // sort the list by value
  entryList.sort((x, y) -> x.getValue().compareTo(y.getValue()));
  System.out.println(entryList);

  // populate the new hash map
  for (Entry<String, Integer> e : entryList)
   sortedByValue.put(e.getKey(), e.getValue());

  return sortedByValue;
 }

}

Làm thế nào để xóa một ký tự hoàn toàn ra khỏi một chuỗi cho trước trong Java?

Lớp String không có phương thức để xóa ký tự. Đoạn code sử dụng phương thức replace() để tạo một chuỗi mới không có ký tự đã cho:

String str1 = "abcdABCDabcdABCD";
  
str1 = str1.replace("a", ""); 

System.out.println(str1); // bcdABCDbcdABCD

String trong Java có tính bất biến. Tất cả các phương thức xử lý chuỗi đều trả về một chuỗi mới. Đó là lý do tại sao ta cần gán nó cho một biến khác.

Làm thế nào để lấy danh sách các ký tự và số lần xuất hiện của chúng trong một chuỗi ở Java?

Ta có thể tạo mảng ký tự từ chuỗi. Sau đó, duyệt qua nó và tạo một Map với ký tự làm khóa (key) và số lần xuất hiện của chúng làm giá trị (value). Đoạn code ví dụ sau cho thấy cách trích xuất và đếm các ký tự của một chuỗi:

String str1 = "abcdABCDabcd";

char[] chars = str1.toCharArray();

Map<Character, Integer> charsCount = new HashMap<>();

for (char c : chars) {
 if (charsCount.containsKey(c)) {
  charsCount.put(c, charsCount.get(c) + 1);
 } else
  charsCount.put(c, 1);
}

System.out.println(charsCount); // {a=2, A=1, b=2, B=1, c=2, C=1, d=2, D=1}

Bạn có thể dùng code chứng minh rằng một đối tượng String trong Java là bất biến (immutable) không?

Đoạn code ví dụ sau có thể chứng minh điều đó. Các bình luận trong code giải thích chi tiết hơn lý do.

String s1 = "Java"; // Chuỗi "Java" được tạo trong String pool và s1 sẽ tham chiếu đến nó.

String s2 = s1; // s2 cũng sẽ tham chiếu đến chính đối tượng "Java" trong pool.

System.out.println(s1 == s2); // chứng tỏ s1 và s2 cùng trỏ đến một tham chiếu

s1 = "Python"; 
// giá trị của s1 đã thay đổi, vậy tại sao nói String là bất biến?

// Trong trường hợp trên, một đối tượng String "Python" mới đã được tạo ra trong pool.
// s1 bây giờ tham chiếu đến đối tượng String mới này.
// TUY NHIÊN, đối tượng String "Java" ban đầu vẫn không hề thay đổi và vẫn nằm trong pool.
// s2 vẫn đang tham chiếu đến đối tượng String "Java" ban đầu đó.

// chứng minh s1 và s2 bây giờ trỏ đến hai tham chiếu khác nhau
System.out.println(s1 == s2); 

System.out.println(s2); 
// in ra "Java", cho thấy giá trị của đối tượng String ban đầu không hề thay đổi, vì vậy String có tính bất biến.

Bạn có thể viết một đoạn code để trình bày tính kế thừa (inheritance) trong Java không?

Đoạn code sau sử dụng từ khóa extends để tạo một lớp con của lớp Animal. Lớp mới Dog kế thừa biến từ lớp Animal và thêm vào nhiều code hơn mà chỉ thuộc về lớp Dog.

class Animal {
 String color;
}

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

Minh họa vấn đề kim cương (diamond problem) của đa kế thừa trong Java

Vấn đề kim cương xảy ra khi một class kế thừa từ nhiều class khác, dẫn đến sự không rõ ràng về việc nên thực thi phương thức của class cha nào. Java không cho phép một class kế thừa từ nhiều class chính là để tránh vấn đề này. Nó được minh họa qua ví dụ sau:

interface I {
 void foo();
}
class A implements I {
 public void foo() {}
}

class B implements I {
 public void foo() {}
}

class C extends A, B { // won't compile
 public void bar() {
  super.foo();
 }
}

Minh họa một ví dụ về try-catch trong Java

Đây một ví dụ sử dụng try-catch:

try {
 FileInputStream fis = new FileInputStream("test.txt");
} catch(FileNotFoundException e) {
 e.printStackTrace();
}

Từ Java 7 trở đi, ta cũng có thể catch nhiều exception trong một khối catch duy nhất như ở dưới. Điều này có ích khi ta có cùng một đoạn code trong tất cả các khối catch.

public static void foo(int x) throws IllegalArgumentException, NullPointerException {
 // some code
}

public static void main(String[] args) {
 try {
  foo(10);
 } catch (IllegalArgumentException | NullPointerException e) {
  System.out.println(e.getMessage());
 }
}

Viết một chương trình Java để minh họa NullPointerException

Nếu ta gọi một hàm trên một đối tượng null như trong đoạn code ví dụ sau, nó sẽ gây ra lỗi NullPointerException:

public static void main(String[] args) {
 printString(null, 3);
 
}

static void printString(String s, int count) {
 for (int i = 0; i < count; i++) {
  System.out.println(s.toUpperCase()); // Exception in thread "main" java.lang.NullPointerException
 }
}

Ta nên kiểm tra null trước để tránh lỗi này:

static void printString(String s, int count) {
 if (s == null) return;
 for (int i = 0; i < count; i++) {
  System.out.println(s.toUpperCase());
 }
}

Ta cũng có thể throw ra lỗi IllegalArgumentException tùy theo yêu cầu của dự án.

Làm thế nào để tạo một record trong Java?

Record đã được thêm vào như một tính năng tiêu chuẩn trong Java 16. Nó cho phép ta tạo một lớp POJO mà không cần viết nhiều code. Record tự động tạo ra các phương thức equals(), hashCode(), các phương thức getter, và phương thức toString() cho lớp.

Record có tính final và ngầm mở rộng lớp java.lang.Record. Đoạn code ví dụ sau cho thấy một cách để tạo một record:

import java.util.Map;
 
public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}

Làm thế nào để tạo các khối text trong Java?

Java 15 đã thêm một số tính năng liên quan đến khối text. Bạn có thể tạo các chuỗi đa dòng bằng cách sử dụng chúng. Các chuỗi này phải được viết bên trong một cặp ba dấu ngoặc kép như trong ví dụ sau:

String textBlock = """
  Hi
  Hello
  Yes""";

Nó tương tự như việc tạo một chuỗi như Hi\\\\nHello\\\\nYes.

Minh họa việc sử dụng biểu thức switch và câu lệnh case nhiều label trong Java

Các biểu thức switch đã được thêm vào như một tính năng tiêu chuẩn trong Java 14. Các ví dụ sau đây cho thấy các biểu thức switch cũng như các câu lệnh case nhiều label:

int choice = 2;

int x = switch (choice) {
    case 1, 2, 3:
     yield choice;
    default:
     yield -1;
};

System.out.println("x = " + x); // x = 2

Ta cũng có thể sử dụng các biểu thức lambda trong biểu thức switch.

String day = "TH";
String result = switch (day) {
    case "M", "W", "F" -> "MWF";
    case "T", "TH", "S" -> "TTS";

    default -> {
     if (day.isEmpty())
      yield "Please insert a valid day.";
     else
      yield "Looks like a Sunday.";
    }
};

System.out.println(result); // TTH

Làm thế nào để biên dịch và chạy một lớp Java từ dòng lệnh?

Trong ví dụ này ta sử dụng file Java sau:

public class Test {

public static void main(String args[]) {
  System.out.println("Hi");
 }

}

Ta có thể biên dịch nó bằng lệnh sau trong terminal:

javac Test.java

Để chạy lớp, sử dụng lệnh sau:

java Test

Đối với các phiên bản gần đây, lệnh java cũng sẽ biên dịch chương trình nếu file .class không tồn tại. Nếu lớp nằm trong một package, chẳng hạn như com.example, thì nó phải nằm trong thư mục com/example. Lệnh để biên dịch và chạy nó là:

java com/example/Test.java

Nếu lớp đó cần một số JAR bổ sung để biên dịch và chạy, ta có thể sử dụng tùy chọn -cp. Ví dụ:

java -cp .:~/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar  com/example/Test.java

Làm thế nào để tạo một enum trong Java?

Đoạn code ví dụ ở dưới cho thấy cách tạo một enum cơ bản:

public enum ThreadStates {
 START,
 RUNNING,
 WAITING,
 DEAD;
}

ThreadStates là enum với các trường hằng số cố định là START, RUNNING, WAITING, và DEAD. Tất cả các enum ngầm mở rộng lớp java.lang.Enum và triển các interface SerializableComparable. Enum cũng có các phương thức mà bạn có thể tìm hiểu thêm trong bài viết hướng dẫn riêng về enum trong Java của chúng tôi.

Làm thế nào để sử dụng phương thức forEach() trong Java?

Phương thức forEach() cung cấp một cách viết tắt để thực hiện một hành động nào đó trên tất cả các phần tử của một iterable (đối tượng có thể duyệt qua từng phần tử). Đoạn code sau ví dụ việc duyệt qua các phần tử của một list và in chúng ra:

List<String> list = new ArrayList<>();

Iterator<String> it = list.iterator();

while (it.hasNext()) {
 System.out.println(it.next());
}

Bạn cũng có thể sử dụng phương thức forEach() với một biểu thức lambda để giảm độ dài code như trong ví dụ sau:

List<String> list = new ArrayList<>();

list.forEach(System.out::print);

Làm thế nào để viết một interface với phương thức defaultstatic?

Java 8 đã giới thiệu các phương thức defaultstatic trong các interface. Điều này đã thu hẹp khoảng cách giữa interface và abstract class (lớp trừu tượng).

Đoạn code ở dưới là ví dụ ta có thể viết một interface với phương thức defaultstatic:

public interface Interface1 {
 
 // regular abstract method
 void method1(String str);
 
 default void log(String str) {
  System.out.println("I1 logging::" + str);
 }
 
 static boolean isNull(String str) {
  System.out.println("Interface Null Check");

  return str == null ? true : "".equals(str) ? true : false;
 }

}

Làm thế nào để tạo một functional interface?

Một interface có đúng một phương thức trừu tượng được gọi là một functional interface (giao diện hàm). Tác dụng chính của nó là ta có thể sử dụng các biểu thức lambda để khởi tạo nó và tránh sử dụng các triển khai lớp ẩn danh cồng kềnh. Annotation @FunctionalInterface có tác dụng đánh dấu một interface là functional như trong ví dụ sau:

@FunctionalInterface
interface Foo {
 void test();
}

Cho một ví dụ về việc sử dụng biểu thức lambda trong Java

Runnable là một ví dụ tuyệt vời về functional interface. Ta có thể sử dụng các biểu thức lambda để tạo một runnable, như trong đoạn code sau:

Runnable r1 = () -> System.out.println("My Runnable");

Cho các ví dụ về nạp chồng (overloading) và ghi đè (overriding) trong Java

Khi một lớp có hai hoặc nhiều phương thức cùng tên, chúng được gọi là các phương thức nạp chồng. Đoạn code ví dụ sau cho thấy một phương thức nạp chồng có tên print():

class Foo {
 void print(String s) {
  System.out.println(s);
 }

 void print(String s, int count) {
  while (count > 0) {
   System.out.println(s);
   count--;
  }
 }

}

Khi một phương thức của lớp cha cũng được triển khai trong lớp con, nó được gọi là ghi đè. Đoạn code ví dụ sau cho thấy cách chú thích phương thức print() được triển khai trong cả hai lớp:

class Base {
 void printName() {
  System.out.println("Base Class");
 }
}

class Child extends Base {
 @Override
 void printName() {
  System.out.println("Child Class");
 }
}

Đoán kết quả Output

Hãy tự kiểm tra bằng cách đoán kết quả của các đoạn code sau.

String s1 = "abc";
String s2 = "abc";

System.out.println("s1 == s2 is:" + s1 == s2);

Output

String s3 = "JournalDev";
int start = 1;
char end = 5;

System.out.println(s3.substring(start, end));

Kết quả của câu lệnh trên là false vì toán tử ++ có độ ưu tiên cao hơn toán tử >. Do đó, biểu thức sẽ thành “s1 == s2 is:abc” == “abc”, và kết quả sẽ là false.

HashSet shortSet = new HashSet();

 for (short i = 0; i < 100; i++) {
    shortSet.add(i);
    shortSet.remove(i - 1);
}

System.out.println(shortSet.size());

Output

ourn

Kết quả của câu lệnh đã cho là ourn. Ký tự đầu tiên được tự động ép kiểu thành int. Sau đó, vì chỉ số của ký tự đầu tiên là 0, nó sẽ bắt đầu từ o và in cho đến n. Lưu ý rằng phương thức substring() của String tạo ra một chuỗi con bắt đầu tại chỉ số start và kéo dài đến ký tự tại chỉ số end - 1.

HashSet shortSet = new HashSet();

 for (short i = 0; i < 100; i++) {
    shortSet.add(i);
    shortSet.remove(i - 1);
}

System.out.println(shortSet.size());

Output

100

Kích thước của shortSet là 100. Tính năng autoboxing trong Java có nghĩa là biểu thức i, với kiểu nguyên thủy là short, được chuyển đổi thành một đối tượng Short.

Tương tự, biểu thức i – 1 có kiểu nguyên thủy là int và được autobox thành một đối tượng Integer. Vì không có đối tượng Integer trong HashSet, không có gì bị xóa và kích thước là 100.

try {
 if (flag) {
    while (true) {
     }
    } else {
     System.exit(1);
    }
} finally {
    System.out.println("In Finally");
}

Không có output. Đoạn code này dẫn đến một vòng lặp vô hạn nếu cờ là true và chương trình sẽ thoát nếu cờ là false. Chương trình sẽ không bao giờ chạy tới khối finally.

String str = null;
String str1="abc";

System.out.println(str1.equals("abc") | str.equals(null));

Output

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "<local1>" is null

Câu lệnh in đã cho sẽ throw ra một NullPointerException vì toán tử logic OR đánh giá cả hai vế trước khi trả về kết quả. Vì str là null, phương thức .equals() sẽ gây ra một exception.

Luôn nên sử dụng các toán tử logic đoản mạch, chẳng hạn như && và ||. Chúng đánh giá các giá trị từ trái sang phải. Trong trường hợp này, vì vế đầu tiên sẽ trả về false, nó sẽ bỏ qua việc đánh giá vế thứ hai.

String x = "abc";
String y = "abc";

x.concat(y);

System.out.print(x);

Output

abc

x.concat(" y") tạo ra một chuỗi mới nhưng không được gán cho x, vì vậy giá trị của x không thay đổi.

public class MathTest {

  public void main(String[] args) {    
     int x = 10 * 10 - 10;
     
     System.out.println(x);
    }
   
}

Output

Error: Main method is not static in class MathTest, please define the main method as:
   public static void main(String[] args)

Mặc dù có vẻ như câu hỏi này liên quan đến thứ tự thực thi của các toán tử toán học, thực sự nó kiểm tra việc ta có để ý phương thức main không được khai báo là static.

public class Test {
   
   public static void main(String[] args) {
     try {
      throw new IOException("Hello");
     } catch(IOException | Exception e) {
      System.out.println(e.getMessage());
     }
    }
}

Output

Test.java:5: error: cannot find symbol
      throw new IOException("Hello");
                ^
  symbol:   class IOException
  location: class Test
Test.java:6: error: cannot find symbol
     }catch(IOException | Exception e) {
            ^
  symbol:   class IOException
  location: class Test
2 errors

Đoạn code này gây ra lỗi biên dịch. Exception IOException đã được catch bởi Exception thay thế.

Tìm 5 lỗi trong đoạn code sau

package com.digitalocean.programming-interviews;

public class String Programs {

 static void main(String[10] args) {
  String s = "abc"
  System.out.println(s);
 }
}

Đáp án

  1. Tên package không được chứa dấu gạch nối.
  2. Tên class không được chứa khoảng trắng.
  3. Phương thức main không phải là public, nên nó sẽ không chạy.
  4. Đối số của phương thức main không nên chỉ định kích thước.
  5. Thiếu dấu chấm phẩy ở cuối dòng định nghĩa chuỗi.

Một số câu hỏi khác

So sánh giữa các câu hỏi về collection và stream

Collection và stream là hai khái niệm nền tảng phục vụ cho các mục đích khác nhau trong việc xử lý dữ liệu. Dưới đây là các đặc điểm và điểm khác nhau giữa chúng:

Collection:

  • Một collection là một nhóm các đối tượng có thể được thao tác như một đơn vị duy nhất.
  • Nó là một cấu trúc dữ liệu tĩnh để lưu trữ các phần tử và cung cấp các phương thức để truy cập và thay đổi chúng.
  • Các ví dụ về collection bao gồm List, Set, Map, và Queue.
  • Collection phù hợp cho các trường hợp dữ liệu cần được lưu trữ và thao tác một cách có cấu trúc.

Stream:

  • Một stream là một chuỗi các phần tử có thể được xử lý theo kiểu pipeline.
  • Nó là một cấu trúc dữ liệu động cho phép tạo ra một pipeline các hoạt động để xử lý các phần tử.
  • Stream được thiết kế để xử lý song song và đặc biệt hữu ích cho việc xử lý các tập dữ liệu lớn.
  • Stream phù hợp cho các trường hợp dữ liệu cần được xử lý theo phong cách lập trình hàm nhiều hơn.

Những khác biệt chính:

  • Lưu trữ: Collection lưu trữ các phần tử, trong khi stream không lưu trữ các phần tử mà thay vào đó xử lý chúng một cách trực tiếp ngay lập tức
  • Xử lý: Collection được xử lý tuần tự, trong khi stream có thể được xử lý song song.
  • Lazy vs Eager: Stream có cơ chế xử lý lười (lazy), nghĩa là các toán tử chỉ được chạy khi một toán tử kết thúc được gọi. Collection thì ngược lại. Nó xử lý theo kiểu hăm hở (eager), nghĩa là mọi toán tử đều được thực thi ngay lập tức.

Các thử thách lập trình về đa luồng và đồng bộ trong Java

Dưới đây là một số thử thách lập trình về đa luồng (multithreading) và đồng bộ (concurrency) trong Java để giúp bạn thực hành và cải thiện kỹ năng của mình:

Thử thách 1: Triển khai một lớp singleton an toàn luồng.

Thử thách 2: Viết một chương trình để minh họa việc sử dụng các khối đồng bộ để đạt được an toàn luồng.

Thử thách 3: Triển khai bài toán nhà sản xuất-người tiêu dùng (producer-consumer) bằng cách sử dụng các phương thức wait()notify().

Thử thách 4: Sử dụng ExecutorService của Java để thực thi đồng thời một danh sách các tác vụ.

Thử thách 5: Triển khai một cache an toàn luồng bằng cách sử dụng ConcurrentHashMap.

Thử thách 6: Viết một chương trình để minh họa việc sử dụng các biến atomic (bất khả phân) cho các hoạt động an toàn luồng.

Thử thách 7: Triển khai một trường hợp deadlock và giải thích cách tránh nó.

Thử thách 8: Sử dụng interface Lock của Java để triển khai một khóa tùy chỉnh cho việc đồng bộ hóa luồng.

Thử thách 9: Triển khai một hàng đợi (queue) an toàn luồng bằng cách sử dụng BlockingQueue.

Thử thách 10: Viết một chương trình để minh họa việc sử dụng CompletableFuture cho lập trình bất đồng bộ.

Những thử thách này bao quát các khía cạnh khác nhau của đa luồng và đồng bộ trong Java, bao gồm an toàn luồng, đồng bộ hóa và lập trình bất đồng bộ.

Các câu hỏi thường gặp

Tôi nên nghiên cứu những chủ đề Java nào để chuẩn bị cho việc phỏng vấn?

Với các buổi phỏng vấn Java, việc nắm vững các chủ đề sau là rất cần thiết:

  • Java nền tảng: Các khái niệm OOP, kiểu dữ liệu, toán tử, cấu trúc điều khiển, phương thức, và xử lý exception.
  • Java Collections Framework: List, Set, Map, và Queue.
  • Đa luồng và Đồng bộ: Tạo thread, đồng bộ hóa, và các collection đồng bộ.
  • Thư viện chuẩn Java: Quen thuộc với các lớp như String, StringBuilder, và Arrays.
  • Các quy tắc nên làm theo khi lập trình Java: Tổ chức code, quy ước đặt tên, và tiêu chuẩn lập trình.

Làm thế nào để chuẩn bị cho các cuộc phỏng vấn kỹ thuật Java?

Để chuẩn bị cho các cuộc phỏng vấn kỹ thuật Java, hãy làm theo các bước sau:

  • Ôn lại kiến thức cơ bản: Củng cố lại các khái niệm Java nền tảng cũng như cấu trúc dữ liệu và thuật toán.
  • Thực hành lập trình: Giải các bài toán trên các nền tảng như LeetCode, HackerRank, hoặc CodeWars.
  • Tập trung vào các chủ đề phỏng vấn phổ biến: Nghiên cứu về đa luồng, đồng bộ, và các chủ đề đặc thù của Java như serialization (chuyển đối tượng thành dạng byte để lưu trữ/truyền tải) và reflection (kiểm tra cấu trúc class/object lúc chạy).
  • Chuẩn bị cho các câu hỏi về kinh nghiệm: Sẵn sàng thảo luận về các dự án đã qua, các quyết định thiết kế, và cách tiếp cận giải quyết vấn đề của bạn.
  • Thực hành giải thuật trên bảng: Tập giải thích các khái niệm kỹ thuật và thiết kế hệ thống trực tiếp trên giấy hoặc bảng.

Nên tập trung vào phiên bản Java nào cho các cuộc phỏng vấn?

Đối với các cuộc phỏng vấn Java, bạn nên tập trung vào Java 8 và các tính năng của nó, chẳng hạn như biểu thức lambda, lập trình hàm, và Date and Time API mới. Tuy nhiên, kiến thức về Java 11 và các cải tiến của nó cũng luôn là một lợi thế.

Đa luồng quan trọng như thế nào trong các cuộc phỏng vấn Java?

Đa luồng là một chủ đề cực kỳ quan trọng vì nó là một khía cạnh chính của lập trình Java. Bạn nên chuẩn bị để trả lời các câu hỏi về tạo thread, đồng bộ hóa, deadlock, và các collection đồng bộ. Các khái niệm đa luồng thường được sử dụng để đánh giá khả năng viết code hiệu quả, có khả năng mở rộng và an toàn luồng.

Một số bài toán lập trình hay để thực hành Java là gì?

Dưới đây là một số bài toán Java phổ biến để nâng cao kĩ năng lập trình:

  • Đảo ngược một chuỗi hoặc một mảng
  • Tìm phần tử ở giữa của một danh sách liên kết
  • Triển khai một stack bằng hai queue
  • Tìm phần tử trùng lặp đầu tiên trong một mảng
  • Triển khai một cây tìm kiếm nhị phân
  • Giải quyết bài toán nhà sản xuất-người tiêu dùng bằng đa luồng
  • Triển khai một cache bằng HashMap
  • Tìm tổng lớn nhất của một mảng con

Kết luận

Bộ sưu tập câu hỏi phỏng vấn lập trình Java ở trên bao gồm các chủ đề cả từ sơ cấp đến nâng cao. Nắm vững chúng sẽ giúp bạn chuẩn bị cho cuộc phỏng vấn sắp tới của mình một cách tốt hơn.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất