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 14

Xuất bản trong nhóm
Pháo hoa! Thế giới không ngừng chuyển động và chúng ta không ngừng chuyển động. Trước đây, để trở thành nhà phát triển Java, chỉ cần biết một chút cú pháp Java là đủ, phần còn lại sẽ đến. Theo thời gian, trình độ kiến ​​thức cần thiết để trở thành nhà phát triển Java đã tăng lên đáng kể, cùng với đó là sự cạnh tranh, điều này tiếp tục đẩy mức kiến ​​thức cần thiết thấp hơn lên cao 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 14 - 1Nếu bạn thực sự muốn trở thành một nhà phát triển, bạn cần phải coi đó là điều hiển nhiên và chuẩn bị kỹ lưỡng để nổi bật giữa những người mới bắt đầu như bạn. Những gì chúng ta sẽ làm hôm nay, cụ thể là, chúng ta sẽ tiếp tục phân tích hơn 250 câu hỏi . Trong các bài viết trước, chúng tôi đã xem xét tất cả các câu hỏi ở cấp độ cơ sở và hôm nay chúng tôi sẽ giải quyết các câu hỏi ở cấp độ trung cấp. Mặc dù tôi lưu ý rằng đây không phải là 100% các câu hỏi ở cấp độ trung cấp, nhưng bạn có thể gặp hầu hết chúng trong một cuộc phỏng vấn cấp cơ sở, bởi vì tại những cuộc phỏng vấn như vậy, việc thăm dò chi tiết cơ sở lý thuyết của bạn diễn ra, trong khi đối với học sinh cấp trung học cơ sở thì các câu hỏi tập trung nhiều hơn vào việc thăm dò kinh nghiệm của anh ấ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 14 - 2Nhưng không dài dòng nữa, hãy bắt đầu thôi.

Ở giữa

Là phổ biến

1. Ưu điểm và nhược điểm của OOP khi so sánh với lập trình thủ tục/chức năng là gì?

Có câu hỏi này trong phần phân tích các câu hỏi dành cho Juinior, và theo đó tôi đã trả lời nó. Hãy tìm câu hỏi này và câu trả lời của nó trong phần này của bài viết, câu hỏi 16 và 17.

2. Tập hợp khác với bố cục như thế nào?

Trong OOP, có một số loại tương tác giữa các đối tượng, được thống nhất dưới khái niệm chung là "Mối quan hệ có". Mối quan hệ này chỉ ra rằng một đối tượng là một thành phần của đối tượng khác. Đồng thời, có hai kiểu con của mối quan hệ này: Thành phần - một đối tượng tạo ra một đối tượng khác và thời gian tồn tại của đối tượng khác phụ thuộc vào thời gian tồn tại của người tạo. Tập hợp - một đối tượng nhận được một liên kết (con trỏ) đến một đối tượng khác trong quá trình xây dựng (trong trường hợp này, vòng đời của đối tượng kia không phụ thuộc vào vòng đời của người tạo). Để hiểu rõ hơn, chúng ta hãy xem một ví dụ cụ thể. Chúng tôi có một loại ô tô nhất định - Car , lớp này có các trường nội bộ thuộc loại - Engine danh sách hành khách - List<Passenger> , nó cũng có một phương thức để bắt đầu chuyển động - startMoving() :

public class Car {
 
 private Engine engine;
 private List<Passenger> passengers;
 
 public Car(final List<Passenger> passengers) {
   this.engine = new Engine();
   this.passengers = passengers;
 }
 
 public void addPassenger(Passenger passenger) {
   passengers.add(passenger);
 }
 
 public void removePassengerByIndex(Long index) {
   passengers.remove(index);
 }
 
 public void startMoving() {
   engine.start();
   System.out.println("Машина начала своё движение");
   for (Passenger passenger : passengers) {
     System.out.println("В машине есть пассажир - " + passenger.getName());
   }
 }
}
Trong trường hợp này, Thành phần là kết nối giữa Xe hơiĐộng cơ , vì hiệu suất của xe phụ thuộc trực tiếp vào sự hiện diện của đối tượng động cơ, vì nếu động cơ = null thì chúng ta sẽ nhận được Ngoại lệ NullPointerException . Ngược lại, động cơ không thể tồn tại nếu không có máy (tại sao chúng ta cần động cơ mà không có máy?) và không thể thuộc về nhiều máy tại một thời điểm. Điều này có nghĩa là nếu chúng ta xóa đối tượng Car thì sẽ không còn tham chiếu đến đối tượng Engine nữa và nó sẽ sớm bị xóa bởi Garbage Collector . Như bạn có thể thấy, mối quan hệ này rất chặt chẽ (mạnh mẽ). Tổng hợp là sự kết nối giữa CarPassenger , vì hiệu suất của Car không hề phụ thuộc vào các đối tượng thuộc loại Passenger và số lượng của chúng. Họ có thể rời khỏi xe - RemovePassengerByIndex(Long index) hoặc nhập xe mới - addPassenger(Passenger Passenger) , mặc dù vậy, xe sẽ tiếp tục hoạt động bình thường. Đổi lại, đối tượng Hành khách có thể tồn tại mà không cần đối tượng Xe hơi . Như bạn hiểu, đây là kết nối yếu hơn nhiều so với những gì chúng ta thấy trong bố cục. 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 14 - 3Nhưng đó chưa phải là tất cả, một đối tượng được kết nối bằng tập hợp với một đối tượng khác cũng có thể có một kết nối nhất định với các đối tượng khác tại cùng một thời điểm. Ví dụ: bạn, với tư cách là một sinh viên Java, đang đăng ký các khóa học tiếng Anh, OOP và logarit vào cùng một thời điểm, nhưng đồng thời bạn không phải là một phần cực kỳ cần thiết trong số đó, nếu không có những khóa học này thì không thể hoạt động bình thường (chẳng hạn như một giáo viên).

3. Bạn đã sử dụng những mẫu GoF nào trong thực tế? Cho ví dụ.

Tôi đã trả lời câu hỏi này trước đây nên tôi sẽ chỉ để lại link phân tích , xem câu hỏi đầu tiên. Tôi cũng tìm thấy một bài viết tuyệt vời về các mẫu thiết kế mà tôi thực sự khuyên bạn nên lưu giữ.

4. Đối tượng proxy là gì? Cho ví dụ

Proxy là một mẫu thiết kế cấu trúc cho phép bạn thay thế các đối tượng thay thế đặc biệt, hay nói cách khác là các đối tượng proxy, thay vì các đối tượng thực. Các đối tượng proxy này chặn các cuộc gọi đến đối tượng ban đầu, cho phép chèn một số logic trước hoặc sau khi cuộc gọi được chuyển đến đối tượng ban đầu. 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 14 - 4Ví dụ về việc sử dụng một đối tượng proxy:
  • Là một proxy từ xa - được sử dụng khi chúng ta cần một đối tượng từ xa (một đối tượng trong một không gian địa chỉ khác) cần được biểu diễn cục bộ. Trong trường hợp này, proxy sẽ xử lý việc tạo, mã hóa, giải mã kết nối, v.v., trong khi máy khách sẽ sử dụng nó như thể nó là đối tượng ban đầu nằm trong không gian cục bộ.

  • Là một proxy ảo - được sử dụng khi cần một đối tượng sử dụng nhiều tài nguyên. Trong trường hợp này, đối tượng proxy đóng vai trò giống như hình ảnh của một đối tượng thực nhưng thực sự chưa tồn tại. Khi một yêu cầu thực sự (gọi phương thức) được gửi đến đối tượng này, chỉ khi đó đối tượng ban đầu mới tải và phương thức thực thi. Cách tiếp cận này còn được gọi là khởi tạo lười biếng; điều này có thể rất thuận tiện, vì trong một số trường hợp, đối tượng ban đầu có thể không hữu ích và sau đó sẽ không mất phí để tạo ra nó.

  • Là proxy bảo mật - được sử dụng khi bạn cần kiểm soát quyền truy cập vào một số đối tượng dựa trên quyền của khách hàng. Nghĩa là, nếu một máy khách bị thiếu quyền truy cập cố gắng truy cập vào đối tượng ban đầu, proxy sẽ chặn nó và không cho phép nó.

Hãy xem một ví dụ về proxy ảo: Chúng tôi có một số giao diện xử lý:

public interface Processor {
 void process();
}
Việc triển khai sử dụng quá nhiều tài nguyên nhưng đồng thời có thể không được sử dụng mỗi khi ứng dụng được khởi chạy:

public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
Lớp ủy nhiệm:

public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;
 
 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
Hãy chạy nó trong main :

Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального an object, ещё не сущетсвует
// но при этом есть an object, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, an object оригинал был создан
Tôi lưu ý rằng nhiều khung sử dụng proxy và đối với Spring đây là mẫu chính (Spring được kết hợp từ trong ra ngoài). Đọc thêm về mô hình này ở đâ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 14 - 5

5. Những cải tiến nào đã được công bố trong Java 8?

Những đổi mới do Java 8 mang lại như sau:
  • Các giao diện chức năng đã được thêm vào, hãy đọc về loại quái thú này ở đây .

  • Biểu thức Lambda, có liên quan chặt chẽ đến giao diện chức năng, hãy đọc thêm về cách sử dụng chúng tại đây .

  • Đã thêm API luồng để xử lý thu thập dữ liệu thuận tiện, đọc thêm tại đây .

  • Đã thêm liên kết vào các phương thức .

  • Phương thức forEach() đã được thêm vào giao diện Iterable .

  • Đã thêm API ngày và giờ mới trong gói java.time , phân tích chi tiết tại đây .

  • API đồng thời được cải tiến .

  • Khi thêm một lớp trình bao bọc tùy chọn , được sử dụng để xử lý chính xác các giá trị null, bạn có thể tìm thấy một bài viết hay về chủ đề này tại đây .

  • Thêm khả năng cho các giao diện sử dụng các phương thức tĩnhmặc định (về bản chất, đưa Java đến gần hơn với đa kế thừa), chi tiết hơn tại đây .

  • Đã thêm các phương thức mới vào lớp Collection(removeIf(), Spliterator()) .

  • Những cải tiến nhỏ đối với Java Core.

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 14 - 6

6. Độ gắn kết cao và độ khớp nối thấp là gì? Cho ví dụ.

High Cohesion hay High Cohesion là khái niệm khi một lớp nhất định chứa các phần tử có liên quan chặt chẽ với nhau và được kết hợp cho mục đích của chúng. Ví dụ: tất cả các phương thức trong lớp Người dùng phải thể hiện hành vi của người dùng. Một lớp có độ gắn kết thấp nếu nó chứa các phần tử không liên quan. Ví dụ: lớp Người dùng chứa phương thức xác thực địa chỉ email:

 public class User {
private String name;
private String email;
 
 public String getName() {
   return this.name;
 }
 
 public void setName(final String name) {
   this.name = name;
 }
 
 public String getEmail() {
   return this.email;
 }
 
 public void setEmail(final String email) {
   this.email = email;
 }
 
 public boolean isValidEmail() {
   // некоторая логика валидации емейла
 }
}
Lớp người dùng có thể chịu trách nhiệm lưu trữ địa chỉ email của người dùng, nhưng không chịu trách nhiệm xác thực hoặc gửi email. Do đó, để đạt được tính mạch lạc cao, chúng tôi chuyển phương thức xác thực sang một lớp tiện ích riêng:

public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
Và chúng tôi sử dụng nó khi cần thiết (ví dụ: trước khi lưu người dùng). Khớp nối thấp hoặc Khớp nối thấp là một khái niệm mô tả sự phụ thuộc lẫn nhau ở mức độ thấp giữa các mô-đun phần mềm. Về cơ bản, sự phụ thuộc lẫn nhau là việc thay đổi cái này đòi hỏi phải thay đổi cái kia. Hai lớp có mối liên kết chặt chẽ (hoặc liên kết chặt chẽ) nếu chúng có liên quan chặt chẽ với nhau. Ví dụ: hai lớp cụ thể lưu trữ các tham chiếu với nhau và gọi các phương thức của nhau. Các lớp liên kết lỏng lẻo sẽ dễ phát triển và bảo trì hơn. Vì chúng độc lập với nhau nên chúng có thể được phát triển và thử nghiệm song song. Hơn nữa, chúng có thể được thay đổi, cập nhật mà không ảnh hưởng lẫn nhau. Chúng ta hãy xem một ví dụ về các lớp được kết hợp chặt chẽ. Chúng tôi có một số lớp sinh viên:

public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
Trong đó có một danh sách các bài học:

public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
Mỗi bài học có chứa một liên kết đến học sinh tham dự. Độ bám cực kỳ mạnh mẽ, bạn có nghĩ vậy không? Làm thế nào bạn có thể giảm bớt nó? Trước tiên, hãy đảm bảo rằng học sinh không có danh sách các môn học mà có danh sách các thông tin nhận dạng của họ:

public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
Thứ hai, lớp học không cần biết về tất cả học sinh, vì vậy hãy xóa toàn bộ danh sách của họ:

public class Lesson {
 private Long id;
 private String name;
}
Vì vậy, nó trở nên dễ dàng hơn nhiều và kết nối trở nên yếu hơn nhiều, bạn có nghĩ vậy khô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 14 - 7

OOP

7. Làm cách nào bạn có thể triển khai đa kế thừa trong Java?

Đa kế thừa là một tính năng của khái niệm hướng đối tượng trong đó một lớp có thể kế thừa các thuộc tính từ nhiều lớp cha. Vấn đề phát sinh khi có các phương thức có cùng chữ ký trong cả siêu lớp và lớp con. Khi gọi một phương thức, trình biên dịch không thể xác định phương thức lớp nào sẽ được gọi và ngay cả khi gọi phương thức lớp được ưu tiên. Do đó, Java không hỗ trợ đa kế thừa! Nhưng có một loại kẽ hở mà chúng ta sẽ nói đến sau. Như tôi đã đề cập trước đó, với việc phát hành Java 8, khả năng có các phương thức mặc định đã được thêm vào các giao diện . Nếu lớp triển khai giao diện không ghi đè phương thức này thì việc triển khai mặc định này sẽ được sử dụng (không cần thiết phải ghi đè phương thức mặc định, chẳng hạn như triển khai một phương thức trừu tượng). Trong trường hợp này, có thể triển khai các giao diện khác nhau trong một lớp và sử dụng các phương thức mặc định của chúng. Hãy xem một ví dụ. Chúng tôi có một số giao diện tờ rơi, với phương thức fly() mặc định :

public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
Giao diện walker, với phương thức walk() mặc định :

public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
Giao diện người bơi, với phương thức swim() :

public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
Chà, bây giờ hãy triển khai tất cả những điều này trong một lớp vịt:

public class Duck implements Flyer, Swimmer, Walker {
}
Và hãy chạy tất cả các phương thức của con vịt của chúng ta:

Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
Trong bảng điều khiển, chúng tôi sẽ nhận được:
Tôi đi!!! Tôi đang bay!!! Tôi đang bơi!!!
Điều này có nghĩa là chúng ta đã mô tả chính xác tính đa kế thừa, mặc dù thực tế không phải như vậy. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 14 - 8Tôi cũng sẽ lưu ý rằng nếu một lớp triển khai các giao diện với các phương thức mặc định có cùng tên phương thức và cùng đối số trong các phương thức này thì trình biên dịch sẽ bắt đầu phàn nàn về tính không tương thích, vì nó không hiểu phương thức nào thực sự cần được sử dụng. Có một số cách thoát ra:
  • Đổi tên các phương thức trong giao diện sao cho chúng khác nhau.
  • Ghi đè các phương thức gây tranh cãi như vậy trong lớp triển khai.
  • Kế thừa từ một lớp thực hiện các phương thức gây tranh cãi này (khi đó lớp của bạn sẽ sử dụng chính xác cách triển khai của nó).

8. Sự khác biệt giữa các phương thức cuối cùng, cuối cùng và hoàn thiện() là gì?

cuối cùng là một từ khóa được sử dụng để đặt một ràng buộc trên một lớp, phương thức hoặc biến, một ý nghĩa ràng buộc:
  • Đối với một biến - sau lần khởi tạo ban đầu, biến đó không thể được xác định lại.
  • Đối với một phương thức, phương thức đó không thể bị ghi đè trong một lớp con (lớp kế thừa).
  • Đối với một lớp - lớp đó không thể được kế thừa.
cuối cùng là một từ khóa trước một khối mã, được sử dụng khi xử lý các ngoại lệ, kết hợp với khối thử và cùng nhau (hoặc có thể thay thế cho nhau) với khối bắt. Mã trong khối này được thực thi trong mọi trường hợp, bất kể ngoại lệ có được ném ra hay không. Trong phần này của bài viết, trong câu hỏi 104, các tình huống đặc biệt trong đó khối này sẽ không được thực thi sẽ được thảo luận. Finalize() là một phương thức của lớp Object , được gọi trước khi mỗi đối tượng bị trình thu gom rác xóa, phương thức này sẽ được gọi (cuối cùng) và được sử dụng để dọn sạch các tài nguyên bị chiếm dụng. Để biết thêm thông tin về các phương thức của lớp Object mà mọi đối tượng kế thừa, hãy xem câu hỏi 11 trong phần này của bài viết. Chà, đó 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! Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 14 - 9
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