CodeGym /Blog Java /Ngẫu nhiên /Khám phá các câu hỏi và câu trả lời từ cuộc phỏng vấn xin...
John Squirrels
Mức độ
San Francisco

Khám phá các câu hỏi và câu trả lời từ cuộc phỏng vấn xin việc cho vị trí nhà phát triển Java. Phần 12

Xuất bản trong nhóm
CHÀO! Kiên thức là sức mạnh. Càng có nhiều kiến ​​thức trong cuộc phỏng vấn đầu tiên, bạn sẽ càng cảm thấy tự tin hơn. Khám phá các câu hỏi và câu trả lời từ cuộc phỏng vấn xin việc cho vị trí nhà phát triển Java.  Phần 12 - 1Nếu bạn mang theo một bộ não lớn chứa đầy kiến ​​thức, người phỏng vấn sẽ khó làm bạn bối rối và có thể sẽ ngạc nhiên một cách thú vị. Vì vậy, không dài dòng nữa, hôm nay chúng ta sẽ tiếp tục củng cố cơ sở lý thuyết của bạn bằng cách xem xét các câu hỏi dành cho nhà phát triển Java.

103. Những quy tắc nào áp dụng cho việc kiểm tra ngoại lệ trong quá trình kế thừa?

Nếu tôi hiểu chính xác câu hỏi thì họ đang hỏi về các quy tắc xử lý các ngoại lệ trong quá trình kế thừa. Các quy tắc có liên quan như sau:
  • Một phương thức được ghi đè hoặc triển khai trong phần con/triển khai không thể đưa ra các ngoại lệ được kiểm tra cao hơn trong hệ thống phân cấp so với các ngoại lệ trong phương thức siêu lớp/giao diện.
Ví dụ: giả sử chúng ta có một số giao diện Animal với phương thức ném IOException :

public interface Animal {
   void speak() throws IOException;
}
Khi triển khai giao diện này, chúng tôi không thể hiển thị một ngoại lệ có thể ném tổng quát hơn (ví dụ: Exception , Throwable ), nhưng chúng tôi có thể thay thế ngoại lệ hiện có bằng lớp con, chẳng hạn như FileNotFoundException :

public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • Mệnh đề ném của hàm tạo của lớp con phải bao gồm tất cả các lớp ngoại lệ được hàm tạo của lớp cha ném ra để tạo đối tượng.
Giả sử hàm tạo của lớp Animal đưa ra rất nhiều ngoại lệ:

 public class Animal {
   public Animal() throws ArithmeticException, NullPointerException, IOException {
   }
Sau đó, hàm tạo của lớp con cũng phải ném chúng:

public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Hoặc, giống như các phương thức, bạn có thể chỉ định các ngoại lệ khác, tổng quát hơn. Trong trường hợp của chúng tôi, chúng tôi có thể chỉ ra Exception , vì nó tổng quát hơn và là tổ tiên chung của cả ba ngoại lệ được chỉ ra trong siêu lớp:

public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Bạn có thể viết một số mã trong đó khối cuối cùng không được thực thi không?

Đầu tiên, chúng ta hãy nhớ cuối cùng là gì. Trước đó, chúng ta đã kiểm tra cơ chế bắt ngoại lệ: khối thử chỉ định nơi sẽ bắt ngoại lệ và (các) khối bắt là mã sẽ được gọi khi bắt được ngoại lệ tương ứng. Khối mã thứ ba được đánh dấu bằng từ khóa cuối cùng có thể thay thế hoặc đứng sau khối bắt. Ý tưởng đằng sau khối này là mã của nó luôn được thực thi bất kể điều gì xảy ra trong khối thử hoặc bắt (bất kể có ngoại lệ hay không). Các trường hợp khối này không được thực thi là không thường xuyên và bất thường. Ví dụ đơn giản nhất là khi System.exit(0) được gọi trước khối cuối cùng, do đó kết thúc chương trình:

try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
Ngoài ra còn có một số tình huống khác mà khối cuối cùng sẽ không chạy:
  • Ví dụ: việc chấm dứt chương trình bất thường do lỗi hệ thống nghiêm trọng hoặc một số lỗi khiến ứng dụng gặp sự cố (ví dụ: StackOverflowError , xảy ra khi ngăn xếp ứng dụng bị tràn).

  • Một tình huống khác là khi một luồng daemon đi vào khối thử cuối cùng , nhưng sau đó luồng chính của chương trình kết thúc. Xét cho cùng, các luồng daemon dành cho công việc nền không có mức độ ưu tiên cao hoặc bắt buộc, vì vậy ứng dụng sẽ không đợi chúng hoàn thành.

  • Ví dụ ngớ ngẩn nhất là một vòng lặp vô tận bên trong khối try or Catch - một khi đã ở bên trong, một luồng sẽ bị kẹt ở đó mãi mãi:

    
    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
Câu hỏi này rất phổ biến trong các cuộc phỏng vấn của nhà phát triển cấp dưới, vì vậy bạn nên nhớ một vài tình huống đặc biệt sau. Khám phá các câu hỏi và câu trả lời từ cuộc phỏng vấn xin việc cho vị trí nhà phát triển Java.  Phần 12 - 2

105. Viết một ví dụ trong đó bạn xử lý nhiều ngoại lệ trong một khối bắt duy nhất.

1) Tôi không chắc câu hỏi này đã được hỏi chính xác. Theo như tôi hiểu, câu hỏi này đề cập đến một số khối bắt và một lần thử :

try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
   System.out.print("Oops! There was an exception: " + e);
}
Nếu một ngoại lệ được ném vào khối thử , thì các khối bắt liên quan sẽ cố gắng bắt nó, tuần tự từ trên xuống dưới. Khi ngoại lệ khớp với một trong các khối bắt , mọi khối còn lại sẽ không thể bắt và xử lý nó nữa. Tất cả điều này có nghĩa là các ngoại lệ hẹp hơn được sắp xếp phía trên các ngoại lệ chung hơn trong tập hợp các khối bắt . Ví dụ: nếu khối bắt đầu tiên của chúng ta bắt được lớp Exception , thì mọi khối tiếp theo sẽ không bắt được các ngoại lệ được kiểm tra (nghĩa là các khối còn lại có lớp con của Exception sẽ hoàn toàn vô dụng). 2) Hoặc có lẽ câu hỏi đã được hỏi một cách chính xác. Trong trường hợp đó, chúng ta có thể xử lý các trường hợp ngoại lệ như sau:

try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // Some handling that involves a narrowing type conversion: (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // Some handling that involves a narrowing type conversion: (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // Some handling that involves a narrowing type conversion: (NullPointerException)e
   }
Sau khi sử dụng tính năng bắt ngoại lệ, chúng tôi sẽ cố gắng khám phá loại cụ thể của nó bằng cách sử dụng toán tử instanceof để kiểm tra xem một đối tượng có thuộc một loại nhất định hay không. Điều này cho phép chúng tôi tự tin thực hiện chuyển đổi loại thu hẹp mà không sợ hậu quả tiêu cực. Chúng ta có thể áp dụng một trong hai cách tiếp cận trong cùng một tình huống. Tôi bày tỏ sự nghi ngờ về câu hỏi này chỉ vì tôi không gọi lựa chọn thứ hai là một cách tiếp cận tốt. Theo kinh nghiệm của tôi, tôi chưa bao giờ gặp phải nó và cách tiếp cận đầu tiên liên quan đến nhiều khối bắt đã phổ biến.

106. Toán tử nào cho phép bạn buộc ném một ngoại lệ? Viết một ví dụ

Tôi đã sử dụng nó nhiều lần trong các ví dụ trên, nhưng tôi sẽ lặp lại một lần nữa: từ khóa ném . Ví dụ về việc ném ngoại lệ theo cách thủ công:

throw new NullPointerException();

107. Phương thức chính có thể đưa ra ngoại lệ không? Nếu vậy thì nó sẽ đi đâu?

Trước hết, tôi muốn lưu ý rằng phương thức chính không gì khác hơn là một phương thức thông thường. Có, nó được máy ảo gọi để bắt đầu thực thi một chương trình, nhưng ngoài ra, nó có thể được gọi từ bất kỳ mã nào khác. Điều đó có nghĩa là nó cũng tuân theo các quy tắc thông thường về việc chỉ ra các ngoại lệ đã kiểm tra sau từ khóa Throws :

public static void main(String[] args) throws IOException {
Theo đó, nó có thể đưa ra các ngoại lệ. Khi main được gọi là điểm bắt đầu của chương trình (chứ không phải bằng một số phương thức khác), thì mọi ngoại lệ mà nó ném ra sẽ được UncaughtExceptionHandler xử lý . Mỗi luồng có một trình xử lý như vậy (nghĩa là có một trình xử lý như vậy trong mỗi luồng). Nếu cần, bạn có thể tạo trình xử lý của riêng mình và thiết lập nó bằng cách gọi phương thức public static void main(String[] args) ném IOException {setDefaultUncaughtExceptionHandler trên một public static void main(String[] args) ném IOException {Thread object.

Đa luồng

Khám phá các câu hỏi và câu trả lời từ cuộc phỏng vấn xin việc cho vị trí nhà phát triển Java.  Phần 12 - 3

108. Bạn biết những cơ chế làm việc trong môi trường đa luồng?

Các cơ chế cơ bản cho đa luồng trong Java là:
  • Từ khóa được đồng bộ hóa , là cách để một luồng khóa một phương thức/khối khi nó xâm nhập, ngăn chặn các luồng khác xâm nhập.

  • Từ khóa dễ bay hơi đảm bảo quyền truy cập nhất quán vào một biến được truy cập bởi các luồng khác nhau. Nghĩa là, khi công cụ sửa đổi này được áp dụng cho một biến, tất cả các thao tác để gán và đọc biến đó sẽ trở thành nguyên tử. Nói cách khác, các luồng sẽ không sao chép biến vào bộ nhớ cục bộ của chúng và thay đổi nó. Họ sẽ thay đổi giá trị ban đầu của nó.

  • Runnable — Chúng ta có thể triển khai giao diện này (bao gồm một phương thức run() ) trong một số lớp:

    
    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }

    Và một khi chúng ta tạo một đối tượng của lớp đó, chúng ta có thể bắt đầu một luồng mới bằng cách chuyển đối tượng của chúng ta tới hàm tạo Thread và sau đó gọi phương thức start() :

    
    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();

    Phương thức start chạy phương thức run() đã triển khai trên một luồng riêng biệt.

  • Thread - Chúng ta có thể kế thừa lớp này và ghi đè phương thức chạy của nó :

    
    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }

    Chúng ta có thể bắt đầu một thread mới bằng cách tạo một đối tượng của lớp này và sau đó gọi phương thức start() :

    
    new CustomThread().start();

  • Đồng thời - Đây là gói công cụ để làm việc trong môi trường đa luồng.

    Nó bao gồm:

    • Bộ sưu tập đồng thời - Đây là tập hợp các bộ sưu tập được tạo rõ ràng để làm việc trong môi trường đa luồng.

    • Hàng đợi - Hàng đợi chuyên dụng cho môi trường đa luồng (chặn và không chặn).

    • Bộ đồng bộ hóa - Đây là những tiện ích chuyên dụng để làm việc trong môi trường đa luồng.

    • Người thực thi - Cơ chế tạo nhóm luồng.

    • Khóa - Cơ chế đồng bộ hóa luồng linh hoạt hơn cơ chế tiêu chuẩn (đồng bộ hóa, chờ, thông báo, thông báo Tất cả).

    • Atomics - Các lớp được tối ưu hóa cho đa luồng. Mỗi hoạt động của họ là nguyên tử.

109. Hãy cho chúng tôi biết về việc đồng bộ hóa giữa các luồng. Các phương thức wait(), notification(), notificationAll() và join() dùng để làm gì?

Đồng bộ hóa giữa các luồng là về từ khóa được đồng bộ hóa . Công cụ sửa đổi này có thể được đặt trực tiếp trên khối:

synchronized (Main.class) {
   // Some logic
}
Hoặc trực tiếp trong chữ ký phương thức:

public synchronized void move() {
   // Some logic }
Như tôi đã nói trước đó, đồng bộ hóa là một cơ chế khóa một khối/phương thức đối với các luồng khác khi một luồng đi vào. Hãy coi khối/phương thức mã như một căn phòng. Một số sợi dây tiếp cận căn phòng, đi vào và khóa cửa bằng chìa khóa của nó. Khi những người khác đến gần phòng, họ thấy cửa bị khóa và đợi ở gần đó cho đến khi có phòng. Sau khi luồng đầu tiên hoàn thành công việc trong phòng, nó sẽ mở khóa cửa, rời khỏi phòng và lấy chìa khóa ra. Tôi đã đề cập đến chìa khóa một vài lần là có lý do - bởi vì thứ gì đó tương tự thực sự tồn tại. Đây là một đối tượng đặc biệt có trạng thái bận/rảnh. Mọi đối tượng trong Java đều có một đối tượng như vậy nên khi sử dụng khối được đồng bộ hóa , chúng ta cần sử dụng dấu ngoặc đơn để chỉ ra đối tượng mà mutex sẽ bị khóa:

Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
Chúng ta cũng có thể sử dụng một mutex liên kết với một lớp, như tôi đã làm trong ví dụ đầu tiên ( Main.class ). Suy cho cùng, khi chúng ta sử dụng tính năng đồng bộ hóa trên một phương thức, chúng ta không chỉ định đối tượng mà chúng ta muốn khóa, phải không? Trong trường hợp này, đối với các phương thức không tĩnh, mutex sẽ bị khóa là đối tượng this , tức là đối tượng hiện tại của lớp. Đối với các phương thức tĩnh, mutex được liên kết với lớp hiện tại ( this.getClass(); ) bị khóa. wait() là một phương thức giải phóng mutex và đặt luồng hiện tại vào trạng thái chờ, như thể đang gắn vào màn hình hiện tại (giống như một cái mỏ neo). Do đó, phương thức này chỉ có thể được gọi từ một khối hoặc phương thức được đồng bộ hóa . Nếu không thì nó sẽ chờ đợi điều gì và phát hành cái gì?). Cũng lưu ý rằng đây là một phương thức của lớp Object . Vâng, không phải một, mà là ba:
  • wait() đặt luồng hiện tại vào trạng thái chờ cho đến khi một luồng khác gọi phương thức notification() hoặc notificationAll() trên đối tượng này (chúng ta sẽ nói về các phương thức này sau).

  • wait(long timeout) đặt luồng hiện tại vào trạng thái chờ cho đến khi một luồng khác gọi phương thức notification() hoặc notificationAll() trên đối tượng này hoặc khoảng thời gian được chỉ định bởi thời gian chờ hết hạn.

  • wait(long timeout, int nanos) giống như phương pháp trước đó, nhưng ở đây nanos cho phép bạn chỉ định nano giây (thời gian chờ chính xác hơn).

  • notification() cho phép bạn đánh thức một luồng ngẫu nhiên đang chờ trên khối đồng bộ hóa hiện tại. Một lần nữa, phương thức này chỉ có thể được gọi trong một khối hoặc phương thức được đồng bộ hóa (xét cho cùng, ở những nơi khác sẽ không có ai thức dậy).

  • notificationAll() đánh thức tất cả các luồng đang chờ trên màn hình hiện tại (cũng chỉ được sử dụng trong một khối hoặc phương thức được đồng bộ hóa ).

110. Làm sao chúng ta có thể dừng một sợi dây?

Điều đầu tiên cần nói ở đây là khi run() chạy đến khi hoàn thành, luồng sẽ tự động kết thúc. Nhưng đôi khi chúng ta muốn giết một thread trước thời hạn, trước khi phương pháp này được thực hiện. Vậy ta phải làm sao? Có lẽ chúng ta có thể sử dụng phương thức stop() trên đối tượng Thread ? Không! Phương pháp đó không được dùng nữa và có thể gây ra sự cố hệ thống. Khám phá các câu hỏi và câu trả lời từ cuộc phỏng vấn xin việc cho vị trí nhà phát triển Java.  Phần 12 - 4Vâng, sau đó thì sao? Có hai cách để thực hiện việc này: Đầu tiên , sử dụng cờ boolean nội bộ của nó. Hãy xem một ví dụ. Chúng tôi đã triển khai một chuỗi sẽ hiển thị một cụm từ nhất định trên màn hình cho đến khi chuỗi dừng hoàn toàn:

public class CustomThread extends Thread {
private boolean isActive;
 
   public CustomThread() {
       this.isActive = true;
   }
 
   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
 
   public void stopRunningThread() {
       isActive = false;
   }
}
Việc gọi phương thức stopRunningThread() sẽ đặt cờ nội bộ thành false, khiến phương thức run() chấm dứt. Hãy gọi nó trong main :

System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
Kết quả là chúng ta sẽ thấy một cái gì đó như thế này trong bảng điều khiển:
Chương trình đang bắt đầu... Luồng đang thực thi một số logic... Luồng đang thực thi một số logic... Luồng đang thực thi một số logic... Luồng đang thực thi một số logic... Luồng đang thực thi một số logic... Chuỗi đang thực thi một số logic... Chương trình đang dừng... Chuỗi đã dừng!
Điều đó có nghĩa là chuỗi của chúng tôi đã bắt đầu, in một số thông báo trên bảng điều khiển và sau đó dừng thành công. Lưu ý rằng số lượng tin nhắn hiển thị sẽ thay đổi tùy theo từng lần khởi chạy. Và đôi khi luồng phụ có thể không hiển thị gì cả. Hành vi cụ thể phụ thuộc vào thời gian luồng chính ngủ. Càng ngủ lâu thì khả năng chuỗi phụ không thể hiển thị bất cứ thứ gì càng ít. Với thời gian ngủ là 1 ms, bạn gần như sẽ không bao giờ nhìn thấy tin nhắn. Nhưng nếu bạn đặt thành 20 mili giây thì các tin nhắn hầu như luôn được hiển thị. Khi thời gian ngủ ngắn, luồng đơn giản là không có thời gian để bắt đầu và thực hiện công việc của nó. Thay vào đó, nó sẽ bị dừng ngay lập tức. Cách thứ hai là sử dụng phương thức bị gián đoạn() trên đối tượng Thread . Nó trả về giá trị của cờ bị gián đoạn nội bộ, theo mặc định là sai . Hoặc phương thức ngắt () của nó , đặt cờ này thành đúng (khi cờ đúng , luồng sẽ ngừng chạy). Hãy xem một ví dụ:

public class CustomThread extends Thread {
 
   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
}
Chạy trong chính :

System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
Kết quả của việc chạy này cũng giống như trong trường hợp đầu tiên, nhưng tôi thích cách tiếp cận này hơn: chúng tôi viết ít mã hơn và sử dụng nhiều chức năng tiêu chuẩn, làm sẵn hơn. Vâng, đó là nó cho ngày hôm nay!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION