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 15

Xuất bản trong nhóm
Xin chào Xin chào! Một nhà phát triển Java cần biết bao nhiêu? Bạn có thể tranh luận rất lâu về vấn đề này, nhưng sự thật là tại buổi phỏng vấn, bạn sẽ bị lý thuyết dẫn dắt hết mức. Ngay cả trong những lĩnh vực kiến ​​​​thức mà bạn sẽ không có cơ hội sử dụng trong công việc của mình. 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 15 - 1Chà, nếu bạn là người mới bắt đầu, kiến ​​thức lý thuyết của bạn sẽ được tiếp thu rất nghiêm túc. Vì chưa có kinh nghiệm và thành tựu to lớn nên tất cả những gì còn lại chỉ là kiểm tra sức mạnh của nền tảng kiến ​​thức. Hôm nay chúng ta sẽ tiếp tục củng cố cơ sở này bằng cách xem xét các câu hỏi phỏng vấn phổ biến nhất dành cho các nhà phát triển Java. Hãy bay nào!

Lõi Java

9. Sự khác biệt giữa liên kết tĩnh và liên kết động trong Java là gì?

Tôi đã trả lời câu hỏi này trong bài viết này ở câu hỏi 18 về đa hình tĩnh và động, tôi khuyên bạn nên đọc nó.

10. Có thể sử dụng các biến riêng tư hoặc được bảo vệ trong một giao diện không?

Không, bạn không thể. Bởi vì khi bạn khai báo một giao diện, trình biên dịch Java sẽ tự động thêm các từ khóa publicabstract trước các phương thức giao diện và các từ khóa public , staticend trước các thành viên dữ liệu. Trên thực tế, nếu bạn thêm riêng tư hoặc được bảo vệ , xung đột sẽ phát sinh và trình biên dịch sẽ phàn nàn về công cụ sửa đổi truy cập với thông báo: “Công cụ sửa đổi '<công cụ sửa đổi đã chọn>' không được phép ở đây.” Tại sao trình biên dịch lại thêm công khai , tĩnhcuối cùng các biến trong giao diện? Hãy tìm ra nó:
  • public - giao diện cho phép client tương tác với đối tượng. Nếu các biến không được công khai, khách hàng sẽ không có quyền truy cập vào chúng.
  • tĩnh - không thể tạo giao diện (hay đúng hơn là các đối tượng của chúng), vì vậy biến là tĩnh.
  • cuối cùng - vì giao diện được sử dụng để đạt được mức độ trừu tượng 100% nên biến có dạng cuối cùng (và sẽ không bị thay đổi).

11. Classloader là gì và dùng để làm gì?

Trình nạp lớp - hoặc Trình nạp lớp - cung cấp khả năng tải các lớp Java. Chính xác hơn, việc tải được đảm bảo bởi các hậu duệ của nó - các trình nạp lớp cụ thể, bởi vì Bản thân ClassLoader là trừu tượng. Ví dụ: mỗi khi tệp .class được tải, sau khi gọi hàm tạo hoặc phương thức tĩnh của lớp tương ứng, hành động này được thực hiện bởi một trong các lớp con của lớp ClassLoader . Có ba loại người thừa kế:
  1. Bootstrap ClassLoader là một trình tải cơ bản, được triển khai ở cấp JVM và không có phản hồi từ môi trường thời gian chạy, vì nó là một phần của hạt nhân JVM và được viết bằng mã gốc. Trình tải này đóng vai trò là cha mẹ của tất cả các phiên bản ClassLoader khác.

    Chịu trách nhiệm chính về việc tải các lớp nội bộ của JDK, thường là rt.jar và các thư viện cốt lõi khác nằm trong thư mục $JAVA_HOME/jre/lib . Các nền tảng khác nhau có thể có cách triển khai trình nạp lớp này khác nhau.

  2. Trình nạp lớp mở rộng là trình tải mở rộng, hậu duệ của lớp trình tải cơ sở. Đảm nhiệm việc tải phần mở rộng của các lớp cơ sở Java tiêu chuẩn. Được tải từ thư mục tiện ích mở rộng JDK, thường là $Java_HOME/lib/ext hoặc bất kỳ thư mục nào khác được đề cập trong thuộc tính hệ thống java.ext.dirs (tùy chọn này có thể được sử dụng để kiểm soát việc tải tiện ích mở rộng).

  3. System ClassLoader là một trình tải hệ thống được triển khai ở cấp JRE, đảm nhiệm việc tải tất cả các lớp cấp ứng dụng vào JVM. Nó tải các tập tin được tìm thấy trong tùy chọn dòng lệnh -classpath hoặc -cp môi trường lớp .

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 15 - 2Trình nạp lớp là một phần của thời gian chạy Java. Thời điểm JVM yêu cầu một lớp, trình nạp lớp sẽ cố gắng tìm lớp đó và tải định nghĩa lớp vào thời gian chạy bằng cách sử dụng tên đầy đủ của lớp đó. Phương thức java.lang.ClassLoader.loadClass() chịu trách nhiệm tải định nghĩa lớp khi chạy. Nó cố gắng tải một lớp dựa trên tên đầy đủ của nó. Nếu lớp chưa được tải, nó sẽ ủy quyền yêu cầu cho trình nạp lớp cha. Quá trình này xảy ra đệ quy và trông như thế này:
  1. Trình nạp lớp hệ thống cố gắng tìm lớp trong bộ đệm của nó.

    • 1.1. Nếu lớp được tìm thấy, quá trình tải đã hoàn tất thành công.

    • 1.2. Nếu không tìm thấy lớp, việc tải sẽ được ủy quyền cho Trình nạp lớp mở rộng.

  2. Trình nạp lớp mở rộng cố gắng tìm lớp trong bộ nhớ đệm của chính nó.

    • 2.1. Nếu lớp được tìm thấy, nó hoàn thành thành công.

    • 2.2. Nếu không tìm thấy lớp, việc tải sẽ được ủy quyền cho Trình nạp lớp Bootstrap.

  3. Bootstrap Classloader cố gắng tìm lớp trong bộ đệm của chính nó.

    • 3.1. Nếu lớp được tìm thấy, quá trình tải đã hoàn tất thành công.

    • 3.2. Nếu không tìm thấy lớp, Trình nạp lớp Bootstrap cơ bản sẽ cố tải nó.

  4. Nếu tải:

    • 4.1. Thành công - việc tải lớp đã hoàn tất.

    • 4.2. Nếu thất bại, quyền điều khiển sẽ được chuyển đến Trình nạp lớp mở rộng.

  5. 5. Trình nạp lớp mở rộng cố gắng tải lớp và nếu tải:

    • 5.1. Thành công - việc tải lớp đã hoàn tất.

    • 5.2. Nếu thất bại, quyền điều khiển sẽ được chuyển đến Trình nạp lớp hệ thống.

  6. 6. Trình nạp lớp hệ thống cố gắng tải lớp và nếu tải:

    • 6.1. Thành công - quá trình tải lớp đã hoàn tất.

    • 6.2. Không vượt qua thành công - một ngoại lệ được tạo ra - ClassNotFoundException.

Chủ đề về trình nạp lớp là một chủ đề rộng lớn và không nên bỏ qua. Để làm quen với nó chi tiết hơn, tôi khuyên bạn nên đọc bài viết này , và chúng ta sẽ không nán lại và tiếp tục.

12. Vùng dữ liệu thời gian chạy là gì?

Ares dữ liệu thời gian chạy - Vùng dữ liệu thời gian chạy JVM. JVM xác định một số vùng dữ liệu thời gian chạy cần thiết trong quá trình thực thi chương trình. Một số trong số chúng được tạo khi JVM khởi động. Một số khác là luồng cục bộ và chỉ được tạo khi luồng được tạo (và bị hủy khi luồng bị hủy). Các vùng dữ liệu thời gian chạy JVM trông như thế nà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 15 - 3
  • Thanh ghi PC là cục bộ của mỗi luồng và chứa địa chỉ của lệnh JVM mà luồng hiện đang thực thi.

  • Ngăn xếp JVM là vùng bộ nhớ được sử dụng làm nơi lưu trữ các biến cục bộ và kết quả tạm thời. Mỗi luồng có ngăn xếp riêng: ngay khi luồng kết thúc, ngăn xếp này cũng bị hủy. Điều đáng chú ý là lợi thế của ngăn xếp so với heap là hiệu suất, trong khi heap chắc chắn có lợi thế về quy mô lưu trữ.

  • Ngăn xếp phương thức gốc - Vùng dữ liệu trên mỗi luồng lưu trữ các phần tử dữ liệu, tương tự như ngăn xếp JVM, để thực thi các phương thức gốc (không phải Java).

  • Heap - được tất cả các luồng sử dụng như một kho lưu trữ chứa các đối tượng, siêu dữ liệu lớp, mảng, v.v., được tạo trong thời gian chạy. Vùng này được tạo khi JVM khởi động và bị hủy khi nó tắt.

  • Vùng phương thức - Vùng thời gian chạy này dùng chung cho tất cả các luồng và được tạo khi JVM khởi động. Nó lưu trữ các cấu trúc cho mỗi lớp, chẳng hạn như Nhóm hằng số thời gian chạy, mã cho các hàm tạo và phương thức, dữ liệu phương thức, v.v.

13. Đối tượng bất biến là gì?

Phần này của bài viết ở câu 14 và 15 đã có đáp án cho câu hỏi này rồi, các bạn hãy xem qua để không lãng phí thời gian nhé.

14. Lớp String có gì đặc biệt?

Trước đó trong phần phân tích, chúng tôi đã nói đi nói lại nhiều lần về một số tính năng nhất định của String (có một phần riêng cho vấn đề này). Bây giờ hãy tóm tắt các tính năng của String :
  1. Nó là đối tượng phổ biến nhất trong Java và được sử dụng cho nhiều mục đích khác nhau. Về tần suất sử dụng, nó không hề thua kém kể cả những loại nguyên thủy.

  2. Một đối tượng của lớp này có thể được tạo mà không cần sử dụng từ khóa mới - trực tiếp thông qua dấu ngoặc kép String str = “string”; .

  3. Stringmột lớp bất biến: khi tạo một đối tượng của lớp này, dữ liệu của nó không thể thay đổi được (khi bạn thêm + “chuỗi khác” vào một chuỗi nhất định, kết quả bạn sẽ nhận được một chuỗi mới, thứ ba). Tính bất biến của lớp String làm cho nó trở nên an toàn.

  4. Lớp String đã được hoàn thiện (có công cụ sửa đổi cuối cùng ) nên không thể kế thừa.

  5. Chuỗi có nhóm chuỗi riêng, một vùng bộ nhớ trong heap lưu trữ các giá trị chuỗi mà nó tạo ra. Trong phần này của loạt bài , ở câu hỏi 62, tôi đã mô tả nhóm chuỗi.

  6. Java có các điểm tương tự của String , cũng được thiết kế để hoạt động với các chuỗi - StringBuilderStringBuffer , nhưng có điểm khác biệt là chúng có thể thay đổi được. Bạn có thể đọc thêm về họ trong bài viết nà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 15 - 4

15. Hiệp phương sai kiểu là gì?

Để hiểu hiệp phương sai, chúng ta sẽ xem xét một ví dụ. Giả sử chúng ta có một lớp động vật:
public class Animal {
 void voice() {
   System.out.println("*тишина*");
 }
}
Và một số lớp Dog mở rộng nó :
public class Dog extends Animal {

 @Override
 public void voice() {
   System.out.println("Гав, гав, гав!!!");
 }
}
Như chúng ta nhớ, chúng ta có thể dễ dàng gán các đối tượng thuộc loại người thừa kế cho loại cha mẹ:
Animal animal = new Dog();
Điều này sẽ không gì khác hơn là tính đa hình. Tiện lợi, linh hoạt phải không? Vâng, còn danh sách các loài động vật thì sao? Chúng ta có thể đưa ra một danh sách có Động vật chung chung có danh sách có đối tượng Chó không?
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
Trong trường hợp này, dòng gán danh sách chó vào danh sách động vật sẽ được gạch chân màu đỏ, tức là. trình biên dịch sẽ không chuyển mã này. Mặc dù thực tế là việc gán này có vẻ khá logic (xét cho cùng, chúng ta có thể gán một đối tượng Dog cho một biến kiểu Animal ), nhưng nó không thể thực hiện được. Điều này là do nếu được cho phép, chúng ta sẽ có thể đưa một đối tượng Động vật vào danh sách ban đầu dự định là Chó , trong khi nghĩ rằng chúng ta chỉ có Chó trong danh sách . Và sau đó, ví dụ, chúng ta sẽ sử dụng phương thức get() để lấy một đối tượng từ danh sách chó đó , nghĩ rằng đó là một con chó và gọi một số phương thức của đối tượng Dog trên đó, mà Animal không có . Và như bạn hiểu, điều này là không thể - sẽ xảy ra lỗi. Nhưng may mắn thay, trình biên dịch không bỏ sót lỗi logic này khi gán danh sách con cháu cho danh sách cha mẹ (và ngược lại). Trong Java, bạn chỉ có thể gán các đối tượng danh sách cho các biến danh sách có các tổng quát phù hợp. Điều này được gọi là sự bất biến. Nếu họ có thể làm điều này, nó sẽ được gọi và được gọi là hiệp phương sai. Nghĩa là, hiệp phương sai là nếu chúng ta có thể đặt một đối tượng thuộc loại ArrayList<Dog> thành một biến thuộc loại List<Animal> . Hóa ra hiệp phương sai không được hỗ trợ trong Java? Cho dù nó thế nào đi chăng nữa! Nhưng điều này được thực hiện theo cách đặc biệt của riêng nó. Thiết kế được sử dụng để làm gì ? mở rộng Động vật . Nó được đặt cùng với một biến chung của biến mà chúng ta muốn đặt đối tượng danh sách, với một biến chung của biến con. Cấu trúc chung này có nghĩa là bất kỳ loại nào là hậu duệ của loại Động vật sẽ làm được (và loại Động vật cũng nằm trong phạm vi khái quát này). Đổi lại, Animal không chỉ có thể là một lớp mà còn là một giao diện (đừng bị đánh lừa bởi từ khóa mở rộng ). Chúng ta có thể thực hiện bài tập trước như thế nà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 15 - 5
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
Kết quả là bạn sẽ thấy trong IDE trình biên dịch sẽ không phàn nàn về cấu trúc này. Hãy kiểm tra chức năng của thiết kế này. Giả sử chúng ta có một phương pháp khiến tất cả động vật được truyền cho nó phát ra âm thanh:
public static void animalsVoice(List<? extends Animal> animals) {
 for (Animal animal : animals) {
   animal.voice();
 }
}
Hãy đưa cho anh ta một danh sách những con chó:
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
animalsVoice(dogs);
Trong bảng điều khiển, chúng ta sẽ thấy kết quả đầu ra sau:
Gâu gâu gâu gâu!!! Gâu gâu gâu gâu!!! Gâu gâu gâu gâu!!!
Điều này có nghĩa là cách tiếp cận hiệp phương sai này hoạt động thành công. Hãy để tôi lưu ý rằng cái chung này có được đưa vào danh sách không ? mở rộng Động vật, chúng tôi không thể chèn dữ liệu mới thuộc bất kỳ loại nào: không phải loại Chó hay thậm chí là loại Động vật :
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
Trên thực tế, ở hai dòng cuối cùng, trình biên dịch sẽ đánh dấu phần chèn đối tượng bằng màu đỏ. Điều này là do thực tế là chúng ta không thể chắc chắn một trăm phần trăm danh sách đối tượng thuộc loại nào sẽ được gán vào danh sách có dữ liệu theo <? mở rộng Động vật> . 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 15 - 6Tôi cũng muốn nói về contravariance , vì thông thường khái niệm này luôn đi cùng với hiệp phương sai và theo quy luật, chúng được hỏi về chúng cùng nhau. Khái niệm này hơi trái ngược với hiệp phương sai, vì cấu trúc này sử dụng kiểu thừa kế. Giả sử chúng ta muốn một danh sách có thể được gán danh sách các đối tượng loại không phải là tổ tiên của đối tượng Dog . Tuy nhiên, chúng tôi không biết trước những loại cụ thể này sẽ là gì. Trong trường hợp này, việc xây dựng biểu mẫu ? super Dog , loại nào cũng phù hợp - tổ tiên của lớp Dog :
List<Animal> animals = new ArrayList<>();
List<? super Dog> dogs = animals;
dogs.add(new Dog());
dogs.add(new Dog());
Chúng ta có thể thêm các đối tượng thuộc loại Dog vào danh sách một cách an toàn với một cái chung như vậy , bởi vì trong mọi trường hợp, nó có tất cả các phương thức được triển khai của bất kỳ tổ tiên nào của nó. Nhưng chúng tôi sẽ không thể thêm một đối tượng thuộc loại Animal , vì không có gì chắc chắn rằng sẽ có các đối tượng thuộc loại này bên trong chứ không phải Dog . Rốt cuộc, chúng ta có thể yêu cầu một thành phần của danh sách này một phương thức của lớp Dog , mà Animal sẽ không có . Trong trường hợp này, lỗi biên dịch sẽ xảy ra. Ngoài ra, nếu chúng ta muốn triển khai phương thức trước đó, nhưng với phương thức chung này:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Dog dog : dogs) {
   dog.voice();
 }
}
chúng ta sẽ gặp lỗi biên dịch trong vòng lặp for , vì chúng ta không thể chắc chắn rằng danh sách trả về có chứa các đối tượng thuộc loại Dog và được tự do sử dụng các phương thức của nó. Nếu chúng ta gọi phương thức dogs.get(0) trong danh sách này . - chúng ta sẽ nhận được một đối tượng thuộc loại Object . Nghĩa là, để phương thức AnimalsVoice() hoạt động , ít nhất chúng ta cần thêm các thao tác nhỏ với việc thu hẹp loại dữ liệu:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Object obj : dogs) {
   if (obj instanceof Dog) {
     Dog dog = (Dog) obj;
     dog.voice();
   }
 }
}
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 15 - 7

16. Lớp Object có các phương thức như thế nào?

Trong phần này của loạt bài, ở đoạn 11, tôi đã trả lời câu hỏi này, vì vậy tôi thực sự khuyên bạn nên đọc nó nếu bạn chưa làm như vậy. Đó là nơi chúng ta sẽ kết thúc ngày hôm nay. Hẹn gặp lại các bạn ở phần tiếp theo! 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 15 - 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