JavaRush /Blog Java /Random-VI /Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấ...

Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java. Phần 12

Xuất bản trong nhóm
Xin chào! Kiên thức là sức mạnh. Bạn càng có nhiều kiến ​​thức trước cuộc phỏng vấn đầu tiên, bạn sẽ càng cảm thấy tự tin hơn. Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 1Với lượng kiến ​​​​thức tốt, bạn sẽ khó nhầm lẫn, đồng thời có thể gây ngạc nhiên thú vị cho người phỏng vấn. Do đó, hôm nay, không dài dòng nữa, chúng tôi sẽ tiếp tục củng cố cơ sở lý thuyết của bạn bằng cách kiểm tra hơn 250 câu hỏi dành cho nhà phát triển Java . Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 2

103. Quy tắc kiểm tra ngoại lệ trong kế thừa là gì?

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 làm việc với các ngoại lệ trong quá trình kế thừa và chúng như sau:
  • Một phương thức được ghi đè hoặc triển khai trong phương thức 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.
Tức là, nếu chúng ta có một giao diện Animal nhất định với phương thức ném IOException :
public  interface Animal {
   void voice() throws IOException;
}
Khi triển khai giao diện này, chúng ta không thể đưa ra một ngoại lệ được ném tổng quát hơn (ví dụ: Exception , Throwable ), nhưng chúng ta có thể thay thế nó bằng một ngoại lệ con cháu, chẳng hạn như FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • Hàm tạo của lớp con phải bao gồm trong khối ném của nó tất cả các lớp ngoại lệ được hàm tạo của lớp cha ném ra, được gọi khi đối tượng được tạo.
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 đó, người thừa kế lớp cũng phải chỉ ra chúng trong hàm tạo:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Hoặc, như trong trường hợp các phương thức, bạn có thể chỉ định không phải các ngoại lệ giống nhau mà là các ngoại lệ chung hơn. Trong trường hợp của chúng ta, chỉ cần chỉ định một ngoại lệ tổng quát hơn - Exception là đủ , vì đây là tổ tiên chung của cả ba ngoại lệ được xem xét:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Bạn có thể viết mã khi 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 đây, chúng ta đã xem xét cơ chế bắt ngoại lệ: khối thử phác thảo khu vực bắt, trong khi (các) khối bắt là mã sẽ hoạt động khi một ngoại lệ cụ thể được ném ra. Cuối cùng là khối mã thứ ba sau khối mã cuối cùng có thể hoán đổi cho nhau bằng lệnh bắt nhưng không loại trừ lẫn nhau. Bản chất của khối này là mã trong nó luôn hoạt động, bất kể kết quả của lần thử hay bắt (bất kể ngoại lệ có được ném hay không). Các trường hợp hỏng hóc của nó rất hiếm và chúng là bất thường. Trường hợp lỗi đơn giản nhất là khi phương thức System.exit(0) được gọi trong đoạn mã trên , phương thức này sẽ kết thúc chương trình (dập tắt nó):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
Ngoài ra còn có một số tình huống khác mà cuối cùng sẽ không hiệu quả:
  • Việc chấm dứt chương trình một cách bất thường do các sự cố nghiêm trọng của hệ thống hoặc do một số Lỗi nào đó sẽ "làm hỏng" ứng dụng (ví dụ về lỗi có thể giống như lỗi StackOwerflowError xảy ra khi bộ nhớ ngăn xếp đầy).
  • Khi luồng deamon đi qua khối ry...cuối cùng và song song với việc này thì chương trình sẽ kết thúc. Xét cho cùng, luồng deamon là một luồng cho các hành động nền, nghĩa là nó không phải là ưu tiên và bắt buộc, và ứng dụng sẽ không đợi công việc của nó kết thúc.
  • Vòng lặp vô hạn phổ biến nhất, trong try hoặc Catch , khi đó luồng sẽ tồn tại ở đó mãi mãi:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

Câu hỏi này khá phổ biến trong các cuộc phỏng vấn dành cho người mới, vì vậy bạn nên ghi nhớ một số tình huống đặc biệt sau đây. Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 3

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

1) Có lẽ câu hỏi đã được hỏi sai. Theo tôi hiểu, câu hỏi này có nghĩa là nhiều lần bắt cho một khối thử :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
Nếu ngoại lệ xảy ra trong khối try thì các khối Catch lần lượt bắt ngoại lệ từ trên xuống dưới, nếu khối Catch nào đó thành công thì nó có quyền xử lý ngoại lệ đó, còn các khối còn lại bên dưới sẽ không còn ngoại lệ nữa. có thể cố gắng nắm bắt và xử lý nó theo cách riêng của họ. Do đó, các ngoại lệ hẹp hơn được đặt cao hơn trong chuỗi khối bắt và các ngoại lệ rộng hơn được đặt thấp hơn. Ví dụ: nếu trong khối bắt đầu tiên của chúng ta, một ngoại lệ của lớp Exception bị bắt , thì các ngoại lệ đã kiểm tra sẽ không thể đi vào các khối còn lại (các khối còn lại có hậu duệ Exception sẽ hoàn toàn vô dụng). 2) Câu hỏi đã được hỏi chính xác. Trong trường hợp này, quá trình xử lý của chúng tôi sẽ như sau:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
Sau khi bắt được một ngoại lệ thông qua Catch , chúng tôi cố gắng tìm ra loại cụ thể của nó thông qua phương thức instanceof , được sử dụng để kiểm tra xem một đối tượng có thuộc một loại nhất định hay không, để sau này chúng tôi có thể thu hẹp nó xuống loại này mà không gây hậu quả tiêu cực. Cả hai phương pháp được xem xét đều có thể được sử dụng trong cùng một tình huống, nhưng tôi đã nói rằng câu hỏi này không chính xác vì tôi không gọi phương án thứ hai là tốt và chưa bao giờ thấy nó trong thực tế của mình, đồng thời phương pháp đầu tiên với phương pháp đánh bắt nhiều lần đã được phổ biến rộng rãi. chú ý. lan rộng. Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 4

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 ở trên, tuy nhiên tôi sẽ lặp lại từ khóa này - ném . Ví dụ sử dụng (buộc một ngoại lệ):
throw new NullPointerException();

107. Phương thức chính có thể ném ngoại lệ ném không? Nếu được thì chuyển đi đâu?

Trước hết, tôi muốn lưu ý rằng main không gì khác hơn là một phương thức thông thường, và vâng, nó được máy ảo gọi để bắt đầu thực thi chương trình, nhưng ngoài điều này, nó có thể được gọi từ bất kỳ mã nào khác. Nghĩa là, nó cũng tuân theo các quy tắc thông thường để chỉ định các ngoại lệ được kiểm tra sau lần ném :
public static void main(String[] args) throws IOException {
Theo đó, những trường hợp ngoại lệ cũng có thể xảy ra trong đó. Nếu main không được gọi trong một số phương thức mà được bắt đầu như một điểm khởi chạy chương trình, thì ngoại lệ do nó ném ra sẽ được xử lý bởi bộ chặn .UncaughtExceptionHandler . Trình xử lý này là một trình xử lý cho mỗi luồng (nghĩa là một trình xử lý 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 phương thức setDefaultUncaughtExceptionHandler được gọi trên đối tượng Thread .

Đa luồng

Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 5

108. Bạn biết những công cụ nào để làm việc với đa luồng?

Các công cụ cơ bản/cơ bản để sử dụng đa luồng trong Java:
  • Đồng bộ hóa là một cơ chế để đóng (chặn) một phương thức/khối khi một luồng đi vào nó, từ các luồng khác.
  • Dễ bay hơi là một cơ chế để đảm bảo quyền truy cập nhất quán vào một biến bằng các luồng khác nhau, nghĩa là với sự hiện diện của công cụ sửa đổi này trên một biến, tất cả các hoạt động gán và đọc phải là nguyên tử. Nói cách khác, các luồng sẽ không sao chép biến này vào bộ nhớ cục bộ của chúng và thay đổi nó mà sẽ thay đổi giá trị ban đầu của nó.
Đọc thêm về biến động ở đây .
  • Runnable là một giao diện có thể được triển khai (cụ thể là phương thức chạy của nó) trong một lớp nhất định:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
Và sau khi đã tạo một đối tượng của lớp này, bạn có thể bắt đầu một luồng mới bằng cách đặt đối tượng này trong hàm tạo của đối tượng Thread mới và gọi phương thức start() của nó :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
Phương thức start chạy phương thức run() đã triển khai trong một luồng riêng biệt.
  • Thread là một lớp, kế thừa từ which (trong khi ghi đè phương thức chạy ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
Và bằng cách tạo một đối tượng của lớp này và khởi chạy nó bằng phương thức start() , do đó chúng ta sẽ khởi chạy một luồng mới:
new CustomThread().start();
  • Đồng thời là một gói có các 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 - một tập hợp các bộ sưu tập chuyên dù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 là các tiện ích chuyên dụng để làm việc trong môi trường đa luồng.
  • Người thực thi là 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áoTất cả).
  • Nguyên tử là các lớp được tối ưu hóa để thực thi đa luồng; mỗi hoạt động là nguyên tử.
Đọc thêm về gói đồng thời tại đây .

109. Nói về sự đồng bộ giữa các thread. Các phương thức wait(), notification() - notificationAll() join() được sử dụng để làm gì?

Theo như tôi hiểu câu hỏi, đồng bộ hóa giữa các luồng là về công cụ sửa đổi khóa - được đồng bộ hóa . Công cụ sửa đổi này có thể được đặt trực tiếp bên cạnh khối:
synchronized (Main.class) {
   // некоторая логика
}
Hoặc trực tiếp trong chữ ký phương thức:
public synchronized void move() {
   // некоторая логика}
Như tôi đã nói trước đó, đồng bộ hóa là một cơ chế cho phép bạn đóng một khối/phương thức khỏi các luồng khác khi một luồng đã nhập vào nó. Hãy coi một khối/phương thức như một căn phòng. Một số luồng khi đến với nó sẽ vào và khóa nó, các luồng khác khi vào phòng và thấy nó đã đóng sẽ đợi gần đó cho đến khi nó rảnh. Sau khi hoàn thành công việc của mình, sợi dây đầu tiên rời khỏi phòng và đưa chìa khóa ra. Và không phải vô cớ mà tôi liên tục nói về chiếc chìa khóa, bởi vì nó 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. Đối tượng này được gắn vào mọi đối tượng Java, vì vậy khi sử dụng khối được đồng bộ hóa , chúng ta cần chỉ ra trong ngoặc đơn đối tượng có mutex mà chúng ta muốn đóng cửa:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
Bạn cũng có thể sử dụng một lớp mutex, như tôi đã làm trong ví dụ đầu tiên ( Main.class ). 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 đóng, phải không? Trong trường hợp này, đối với một phương thức không tĩnh, nó sẽ đóng trên mutex của đối tượng this , tức là đối tượng hiện tại của lớp này. Cái tĩnh sẽ đóng trên mutex của lớp hiện tại ( this.getClass(); ). Bạn có thể đọc thêm về mutex tại đây . Vâng, đọc về đồng bộ hóa ở đây . Wait() là một phương thức giải phóng mutex và đặt luồng hiện tại vào chế độ chờ, như thể được gắn vào màn hình hiện tại (giống như một 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, nó sẽ giải phóng cái gì và nó nên mong đợi cái gì). Cũng lưu ý rằng đây là một phương thức của lớp Object . Chính xác hơn, không phải một, mà thậm chí là ba:
  • Wait() - đặt luồng hiện tại vào chế độ chờ cho đến khi một luồng khác gọi phương thức notification() hoặc notificationAll() cho đối tượng này (chúng ta sẽ nói về các phương thức này sau).

  • Chờ (thời gian chờ lâu) - đặt luồng hiện tại vào chế độ 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 hết thời gian chờ đã chỉ định .

  • Đợi (thời gian chờ dài, int nano) - tương tự như phần trước, chỉ nano cho phép bạn chỉ định nano giây (cài đặt thời gian chính xác hơn).

  • Notify() là phương pháp cho phép bạn đánh thức một luồng ngẫu nhiên của khối đồng bộ hóa hiện tại. Một lần nữa, nó 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, nó sẽ không có ai để giải phóng).

  • NotifyAll() là phương thức đá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 thế nào để ngăn dòng chảy?

Điều đầu tiên cần nói là khi phương thức run() được thực thi đầy đủ , luồng sẽ tự động bị hủy. Nhưng đôi khi bạn cần phải giết hắn trước thời hạn, trước khi phương pháp này hoàn thành. Vậy chúng ta nên làm gì sau đó? Có lẽ đối tượng Thread nên có phương thức stop() ? Cho dù nó thế nào đi chăng nữa! Phương pháp này được coi là lỗi thời và có thể dẫn đến sự cố hệ thống. Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 6Vâng, sau đó thì sao? Có hai cách để thực hiện việc này: Cách thứ nhất là sử dụng cờ boolean nội bộ của bạn. Hãy xem một ví dụ. Chúng tôi có cách triển khai riêng 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 nó 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("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
Khi sử dụng phương thức stopRunning() , cờ nội bộ sẽ trở thành sai và phương thức chạy sẽ ngừng chạy. Hãy chạy nó trong main :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
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:
Bắt đầu thực thi chương trình 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... luồng đang thực thi một số logic... Kết thúc thực thi chương trình Luồng đã dừng!
Điều này có nghĩa là luồng của chúng tôi đã hoạt động, xuất một số lượng thông báo nhất định tới bảng điều khiển và đã dừng thành công. Tôi lưu ý rằng số lượng thông báo đầu ra sẽ thay đổi tùy theo từng lần chạy; đôi khi luồng bổ sung thậm chí không xuất ra bất cứ thứ gì. Như tôi nhận thấy, điều này phụ thuộc vào thời gian ngủ của luồng chính, thời gian ngủ càng dài thì càng ít khả năng luồng bổ sung không xuất ra bất cứ thứ gì. Với thời gian ngủ là 1ms, tin nhắn hầu như không bao giờ được xuất ra, nhưng nếu bạn đặt ở mức 20ms thì nó hầu như luôn hoạt động. Có lẽ, khi thời gian ngắn, luồng đơn giản là không có thời gian để bắt đầu và bắt đầu công việc của nó và ngay lập tức bị dừng lại. Cách thứ hai là sử dụng phương thức bị gián đoạn() trên đối tượng Thread , phương thức này trả về giá trị của cờ ngắt bên trong (cờ này mặc định là sai ) và phương thức ngắt() khác của nó , đặt cờ này thành đúng (khi điều này cờ là đúng thì luồng sẽ dừng hoạt động). Hãy xem một ví dụ:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
Chạy trong chính :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
Kết quả thực thi sẽ 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. Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 7Đó là nơi chúng ta sẽ dừng lại ngày hôm nay.Phân tích các câu hỏi và câu trả lời từ các cuộc phỏng vấn dành cho nhà phát triển Java.  Phần 12 - 8
Các tài liệu khác trong loạt bài:
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION