JavaRush /จาวาบล็อก /Random-TH /การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java ตอนท...
Константин
ระดับ

การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java ตอนที่ 14

เผยแพร่ในกลุ่ม
ดอกไม้เพลิง! โลกเคลื่อนไหวอยู่ตลอดเวลา และเราก็เคลื่อนไหวอยู่ตลอดเวลา ก่อนหน้านี้เพื่อที่จะเป็น Java Developer การรู้ไวยากรณ์ Java เพียงเล็กน้อยก็เพียงพอแล้วที่เหลือจะมาเอง เมื่อเวลาผ่านไป ระดับความรู้ที่จำเป็นในการเป็นนักพัฒนา Java ก็เพิ่มขึ้นอย่างมาก เช่นเดียวกับการแข่งขัน ซึ่งยังคงผลักดันความรู้ที่จำเป็นด้านล่างให้สูงขึ้นอย่างต่อเนื่อง หากการวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 1คุณต้องการเป็น Developer จริงๆ คุณต้องยอมรับและเตรียมพร้อมอย่างถี่ถ้วนเพื่อให้โดดเด่นในหมู่ผู้เริ่มต้นเช่นคุณ สิ่งที่เราจะทำในวันนี้ คือ เราจะวิเคราะห์คำถามมากกว่า250 ข้อต่อไป ในบทความก่อนหน้านี้ เราได้ตรวจสอบคำถามระดับจูเนียร์ทั้งหมดแล้ว และวันนี้เราจะมาตอบคำถามระดับกลาง แม้ว่าฉันจะทราบว่าคำถามเหล่านี้ไม่ใช่คำถามระดับกลาง 100% แต่คุณสามารถพบคำถามส่วนใหญ่ได้ในการสัมภาษณ์ระดับจูเนียร์ เนื่องจากในการสัมภาษณ์นั้นจะมีการซักถามฐานทางทฤษฎีโดยละเอียด ในขณะที่สำหรับนักเรียนระดับกลาง คำถามจะเน้นไปที่การพิสูจน์ประสบการณ์ของเขามากกว่า การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 2แต่เพื่อไม่ให้เป็นการเสียเวลา มาเริ่มกันเลย

กลาง

เป็นเรื่องธรรมดา

1. อะไรคือข้อดีและข้อเสียของ OOP เมื่อเปรียบเทียบกับการเขียนโปรแกรมตามขั้นตอน/ฟังก์ชัน

มีคำถามนี้ในการวิเคราะห์คำถามของรุ่นจูเนียร์ และด้วยเหตุนี้ ฉันก็ตอบไปแล้ว ค้นหาคำถามนี้และคำตอบใน ส่วน นี้ของบทความ คำถามที่ 16 และ 17

2. การรวมกลุ่มแตกต่างจากการจัดองค์ประกอบอย่างไร

ใน OOP มีการโต้ตอบระหว่างวัตถุหลายประเภท ซึ่งรวมกันภายใต้แนวคิดทั่วไปของ "ความสัมพันธ์แบบมี-แบบ" ความสัมพันธ์นี้บ่งชี้ว่าวัตถุหนึ่งเป็นส่วนประกอบของวัตถุอื่น ในขณะเดียวกัน ความสัมพันธ์นี้มีสองประเภทย่อย: องค์ประกอบ - วัตถุหนึ่งสร้างวัตถุอื่น และอายุการใช้งานของวัตถุอื่นขึ้นอยู่กับอายุการใช้งานของผู้สร้าง การรวมกลุ่ม - วัตถุได้รับลิงก์ (ตัวชี้) ไปยังวัตถุอื่นในระหว่างกระบวนการก่อสร้าง (ในกรณีนี้ อายุการใช้งานของวัตถุอื่นไม่ได้ขึ้นอยู่กับอายุการใช้งานของผู้สร้าง) เพื่อความเข้าใจที่ดีขึ้น เรามาดูตัวอย่างที่เฉพาะเจาะจงกัน เรามีคลาสรถยนต์บางประเภท - Carซึ่งจะมีฟิลด์ภายในประเภท - Engine และรายชื่อผู้โดยสาร - List<Passenger>แต่ก็มีวิธีการในการเริ่มการเคลื่อนที่ - 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());
   }
 }
}
ในกรณีนี้องค์ประกอบคือการเชื่อมต่อระหว่างCarและEngineเนื่องจากประสิทธิภาพของรถยนต์ขึ้นอยู่กับการมีอยู่ของวัตถุเครื่องยนต์โดยตรง เพราะถ้าengine = nullเราจะได้รับNullPointerException ในทางกลับกัน เครื่องยนต์ไม่สามารถดำรงอยู่ได้หากไม่มีเครื่องจักร (เหตุใดเราจึงต้องมีเครื่องยนต์ที่ไม่มีเครื่องจักร) และไม่สามารถเป็นของเครื่องจักรหลายเครื่องได้ในคราวเดียว ซึ่งหมายความว่าหากเราลบ วัตถุ Carจะไม่มีการอ้างอิง ถึง วัตถุEngine อีกต่อไป และใน ไม่ ช้า Garbage Collector ก็จะถูกลบทิ้ง อย่างที่คุณเห็นความสัมพันธ์นี้เข้มงวดมาก (แข็งแกร่ง) การรวมกลุ่มคือการเชื่อมโยงระหว่างรถยนต์และผู้โดยสารเนื่องจากประสิทธิภาพของรถยนต์ไม่ได้ขึ้นอยู่กับ ประเภท ผู้โดยสารและจำนวน ผู้โดยสารแต่อย่างใด พวกเขาสามารถออกจากรถ - ลบPassengerByIndex(ดัชนีแบบยาว)หรือป้อนใหม่ - addPassenger(ผู้โดยสารผู้โดยสาร)แม้ว่าจะเป็นเช่นนี้ รถจะยังคงทำงานได้อย่างถูกต้องต่อไป ในทางกลับกัน วัตถุ Passengerสามารถดำรงอยู่ได้โดยไม่มีวัตถุCar ดังที่คุณเข้าใจ นี่เป็นการเชื่อมต่อที่อ่อนแอกว่าที่เราเห็นในองค์ประกอบภาพมาก การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 3แต่นั่นไม่ใช่ทั้งหมด วัตถุที่เชื่อมต่อกันด้วยการรวมกลุ่มกับอีกวัตถุหนึ่งสามารถมีการเชื่อมต่อกับวัตถุอื่น ๆ ในช่วงเวลาเดียวกันได้ ตัวอย่างเช่น คุณในฐานะนักเรียน Java ได้ลงทะเบียนหลักสูตรภาษาอังกฤษ OOP และลอการิทึมในเวลาเดียวกัน แต่ในขณะเดียวกัน คุณก็ไม่ใช่ส่วนสำคัญอย่างยิ่งของหลักสูตรเหล่านี้ โดยที่การทำงานปกติจะเป็นไปไม่ได้ (เช่น ครู).

3. คุณใช้รูปแบบ GoF ใดในทางปฏิบัติ? ยกตัวอย่าง.

ฉันเคยตอบคำถามนี้ไปแล้ว ดังนั้นฉันจะทิ้งลิงก์ไปยังการวิเคราะห์ดูคำถามแรก ฉันยังพบบทความโกง ที่ยอดเยี่ยม เกี่ยวกับรูปแบบการออกแบบ ซึ่งฉันขอแนะนำอย่างยิ่งให้เก็บไว้

4. อ็อบเจ็กต์พร็อกซีคืออะไร? ยกตัวอย่าง

พร็อกซีเป็นรูปแบบการออกแบบโครงสร้างที่ช่วยให้คุณสามารถทดแทนวัตถุทดแทนพิเศษ หรืออีกนัยหนึ่งคือวัตถุพร็อกซี แทนวัตถุจริง ออบเจ็กต์พร็อกซีเหล่านี้ดักฟังการเรียกไปยังออบเจ็กต์ต้นฉบับ ทำให้สามารถแทรกตรรกะบางอย่างก่อนหรือหลังการเรียกถูกส่งผ่านไปยังออบเจ็กต์ดั้งเดิม การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 4ตัวอย่างของการใช้วัตถุพร็อกซี:
  • เป็นพร็อกซีระยะไกล - ใช้เมื่อเราต้องการวัตถุระยะไกล (วัตถุในพื้นที่ที่อยู่อื่น) ที่จำเป็นต้องแสดงภายในเครื่อง ในกรณีนี้ พร็อกซีจะจัดการการสร้างการเชื่อมต่อ การเข้ารหัส การถอดรหัส ฯลฯ ในขณะที่ไคลเอ็นต์จะใช้พร็อกซีราวกับว่าเป็นวัตถุดั้งเดิมที่อยู่ในพื้นที่ท้องถิ่น

  • เป็นพร็อกซีเสมือน - ใช้เมื่อต้องการออบเจ็กต์ที่ใช้ทรัพยากรมาก ในกรณีนี้ อ็อบเจ็กต์พร็อกซีจะทำหน้าที่เป็นรูปภาพของวัตถุจริงที่ยังไม่มีอยู่จริง เมื่อคำขอจริง (การเรียกเมธอด) ถูกส่งไปยังออบเจ็กต์นี้ เฉพาะออบเจ็กต์ต้นฉบับเท่านั้นที่โหลดและวิธีการดำเนินการ วิธีการนี้เรียกอีกอย่างว่าการเริ่มต้นแบบ Lazy ซึ่งจะสะดวกมาก เพราะในบางสถานการณ์ ออบเจ็กต์ต้นฉบับอาจไม่มีประโยชน์ และจะไม่มีค่าใช้จ่ายในการสร้างมัน

  • เป็นพร็อกซีความปลอดภัย - ใช้เมื่อคุณต้องการควบคุมการเข้าถึงออบเจ็กต์บางอย่างตามสิทธิ์ของไคลเอ็นต์ นั่นคือ หากไคลเอ็นต์ที่ไม่มีสิทธิ์การเข้าถึงพยายามเข้าถึงออบเจ็กต์ดั้งเดิม พร็อกซีจะดักจับและไม่อนุญาต

ลองดูตัวอย่างของพร็อกซีเสมือน: เรามีอินเทอร์เฟซตัวจัดการบางส่วน:
public interface Processor {
 void process();
}
การใช้งานซึ่งใช้ทรัพยากรมากเกินไป แต่ในขณะเดียวกันก็อาจไม่สามารถใช้ได้ทุกครั้งที่เปิดตัวแอปพลิเคชัน:
public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
คลาสพร็อกซี:
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;

 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
มารันกันที่main :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального an object, ещё не сущетсвует
// но при этом есть an object, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, an object оригинал был создан
ฉันทราบว่าเฟรมเวิร์กจำนวนมากใช้การพร็อกซี และสำหรับSpringนี่เป็นรูปแบบที่สำคัญ (Spring ถูกเย็บเข้าและออก) อ่านเพิ่มเติมเกี่ยวกับรูปแบบนี้ได้ที่นี่ การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 5

5. มีการประกาศนวัตกรรมอะไรบ้างใน Java 8?

นวัตกรรมที่นำโดย Java 8 มีดังนี้:
  • เพิ่มอินเทอร์เฟซการใช้งานแล้ว อ่านเกี่ยวกับสัตว์ร้ายชนิดนี้ได้ที่นี่

  • นิพจน์ Lambda ซึ่งเกี่ยวข้องอย่างใกล้ชิดกับอินเทอร์เฟซการทำงาน อ่านเพิ่มเติมเกี่ยวกับการใช้งานได้ที่นี่

  • เพิ่มStream APIเพื่อการประมวลผลการรวบรวมข้อมูลที่สะดวก อ่านเพิ่มเติมที่นี่

  • เพิ่มลิงค์ไปยังวิธีการ

  • เพิ่มเมธอดforEach()ให้กับ อินเทอร์เฟซ Iterableแล้ว

  • เพิ่มAPI วันที่และเวลา ใหม่ใน แพ็คเกจ java.timeการวิเคราะห์โดยละเอียดที่นี่

  • ปรับปรุงAPI ที่เกิดขึ้นพร้อมกัน

  • เมื่อเพิ่ม คลาส wrapper เสริม ซึ่งใช้เพื่อจัดการค่า Null อย่างถูก ต้องคุณสามารถค้นหาบทความดีๆ เกี่ยวกับหัวข้อนี้ได้ที่นี่

  • การเพิ่มความสามารถสำหรับอินเทอร์เฟซในการใช้ วิธี คงที่และเริ่มต้น (ซึ่งโดยพื้นฐานแล้วทำให้ Java เข้าใกล้การสืบทอดหลายรายการมากขึ้น) รายละเอียดเพิ่มเติมที่นี่

  • เพิ่มวิธีการใหม่ให้กับคลาสCollection(removeIf(), spliterator())

  • การปรับปรุงเล็กน้อยใน Java Core

การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 6

6. การยึดเกาะสูงและการเชื่อมต่อต่ำคืออะไร? ยกตัวอย่าง.

High CohesionหรือHigh Cohesionเป็นแนวคิดเมื่อคลาสบางคลาสมีองค์ประกอบที่เกี่ยวข้องกันอย่างใกล้ชิดและรวมกันเพื่อจุดประสงค์ของมัน ตัวอย่างเช่น วิธีการทั้งหมดใน คลาส Userควรแสดงถึงพฤติกรรมของผู้ใช้ คลาสมีการทำงานร่วมกันต่ำหากมีองค์ประกอบที่ไม่เกี่ยวข้องกัน ตัวอย่างเช่น คลาส Userที่มีวิธีการตรวจสอบที่อยู่อีเมล:
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() {
   // некоторая логика валидации емейла
 }
}
คลาสผู้ใช้อาจรับผิดชอบในการจัดเก็บที่อยู่อีเมลของผู้ใช้ แต่ไม่ใช่สำหรับการตรวจสอบความถูกต้องหรือส่งอีเมล ดังนั้น เพื่อให้เกิดการเชื่อมโยงกันในระดับสูง เราจึงย้ายวิธีการตรวจสอบความถูกต้องไปไว้ในคลาสยูทิลิตี้ที่แยกจากกัน:
public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
และเราใช้ตามความจำเป็น (เช่น ก่อนบันทึกผู้ใช้) ข้อต่อต่ำหรือข้อต่อต่ำเป็นแนวคิดที่อธิบายการพึ่งพาอาศัยกันต่ำระหว่างโมดูลซอฟต์แวร์ โดยพื้นฐานแล้ว การพึ่งพาซึ่งกันและกันคือการเปลี่ยนแปลงสิ่งหนึ่งซึ่งต้องเปลี่ยนอีกสิ่งหนึ่ง สองชั้นมีการมีเพศสัมพันธ์ที่แข็งแกร่ง (หรือการมีเพศสัมพันธ์ที่แน่นหนา) หากมีความสัมพันธ์กันอย่างใกล้ชิด ตัวอย่างเช่น คลาสที่เป็นรูปธรรมสองคลาสที่เก็บการอ้างอิงถึงกันและเรียกเมธอดของกันและกัน คลาสคู่ที่หลวมจะพัฒนาและบำรุงรักษาได้ง่ายกว่า เนื่องจากเป็นอิสระจากกัน จึงสามารถพัฒนาและทดสอบแบบคู่ขนานได้ นอกจากนี้ยังสามารถเปลี่ยนแปลงและปรับปรุงได้โดยไม่กระทบต่อกัน ลองดูตัวอย่างของคลาสที่เชื่อมโยงกันอย่างแน่นหนา เรามีชั้นเรียนของนักเรียน:
public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
ซึ่งมีรายการบทเรียน:
public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
แต่ละบทเรียนมีลิงก์ไปยังนักเรียนที่เข้าร่วม ด้ามจับที่แข็งแกร่งอย่างไม่น่าเชื่อ คุณว่าไหม? คุณจะลดมันได้อย่างไร? ขั้นแรก ตรวจสอบให้แน่ใจว่านักเรียนไม่มีรายชื่อวิชา แต่เป็นรายชื่อตัวระบุ:
public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
ประการที่สอง ชั้นเรียนไม่จำเป็นต้องรู้เกี่ยวกับนักเรียนทุกคน ดังนั้นให้ลบรายชื่อนักเรียนทั้งหมดเลย:
public class Lesson {
 private Long id;
 private String name;
}
ดังนั้นมันจึงง่ายขึ้นมาก และการเชื่อมต่อก็อ่อนแอลงมาก คุณว่ามั้ย? การวิเคราะห์คำถามและคำตอบจากการสัมภาษณ์นักพัฒนา Java  ตอนที่ 14 - 7

อุ๊ย

7. คุณจะใช้การสืบทอดหลายรายการใน Java ได้อย่างไร?

การสืบทอดหลายรายการเป็นคุณลักษณะของแนวคิดเชิงวัตถุซึ่งคลาสสามารถสืบทอดคุณสมบัติจากคลาสพาเรนต์มากกว่าหนึ่งคลาส ปัญหาเกิดขึ้นเมื่อมีเมธอดที่มีลายเซ็นเหมือนกันทั้งในคลาสซุปเปอร์และคลาสย่อย เมื่อเรียกเมธอด คอมไพลเลอร์ไม่สามารถระบุได้ว่าควรเรียกเมธอดคลาสใด และแม้แต่เมื่อเรียกเมธอดคลาสที่มีความสำคัญกว่าก็ตาม ดังนั้น Java จึงไม่รองรับการสืบทอดหลายรายการ! แต่มีช่องโหว่ซึ่งเราจะพูดถึงต่อไป ดังที่ฉันได้กล่าวไว้ก่อนหน้านี้ ด้วยการเปิดตัว Java 8 ความสามารถในการมีวิธีการเริ่มต้นถูกเพิ่มลงใน อิน เทอร์เฟซ หากคลาสที่ใช้อินเทอร์เฟซไม่ได้แทนที่วิธีการนี้ การใช้งานเริ่มต้นนี้จะถูกนำมาใช้ (ไม่จำเป็นต้องแทนที่วิธีการเริ่มต้น เช่น การใช้วิธีนามธรรม) ในกรณีนี้ คุณสามารถปรับใช้อินเทอร์เฟซที่แตกต่างกันในคลาสเดียวและใช้วิธีการเริ่มต้นได้ ลองดูตัวอย่าง เรามีอินเทอร์เฟซของใบปลิว โดยมีเมธอดfly() เริ่มต้น :
public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
อินเทอร์เฟซของวอล์คเกอร์ด้วยเมธอดwalk() เริ่มต้น :
public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
อินเทอร์เฟซนักว่ายน้ำด้วย เมธอด swim() :
public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
ตอนนี้เรามาปรับใช้ทั้งหมดนี้ในคลาสเป็ดเดียว:
public class Duck implements Flyer, Swimmer, Walker {
}
และลองใช้วิธีการทั้งหมดของเป็ดของเรา:
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
ในคอนโซลเราจะได้รับ:
ฉันไป!!! ฉันกำลังบิน!!! ฉันกำลังว่ายน้ำ!!!
ซึ่งหมายความว่าเราได้พรรณนาถึงมรดกหลายรายการอย่างถูกต้อง แม้ว่าสิ่งนี้จะไม่ได้เป็นเช่นนั้นก็ตาม Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 14 - 8ฉันจะทราบด้วยว่าหากคลาสใช้อินเทอร์เฟซกับวิธีการเริ่มต้นที่มีชื่อวิธีการเดียวกันและอาร์กิวเมนต์เดียวกันในวิธีการเหล่านี้ คอมไพเลอร์จะเริ่มบ่นเกี่ยวกับความไม่เข้ากัน เนื่องจากไม่เข้าใจว่าจำเป็นต้องใช้วิธีใดจริงๆ มีหลายวิธี:
  • เปลี่ยนชื่อวิธีการในอินเทอร์เฟซเพื่อให้แตกต่างกัน
  • แทนที่วิธีการขัดแย้งดังกล่าวในคลาสการใช้งาน
  • สืบทอดจากคลาสที่ใช้วิธีการที่มีการโต้แย้งเหล่านี้ (จากนั้นคลาสของคุณจะใช้การนำไปใช้งานทุกประการ)

8. อะไรคือความแตกต่างระหว่างเมธอด Final, ในที่สุด และ Finalize()?

Finalเป็นคีย์เวิร์ดที่ใช้เพื่อกำหนดข้อจำกัดในคลาส เมธอด หรือตัวแปร ซึ่งหมายถึงข้อจำกัด:
  • สำหรับตัวแปร - หลังจากการกำหนดค่าเริ่มต้นแล้ว ตัวแปรจะไม่สามารถกำหนดใหม่ได้
  • สำหรับวิธีการนั้น ไม่สามารถแทนที่วิธีการในคลาสย่อยได้ (คลาสที่สืบทอด)
  • สำหรับคลาส - ไม่สามารถสืบทอดคลาสได้
สุดท้ายคือคีย์เวิร์ดที่อยู่หน้าบล็อกของโค้ด ซึ่งใช้เมื่อจัดการกับข้อยกเว้น ร่วมกับtry block และใช้ร่วมกับ catch block (หรือสลับกันได้) รหัสในบล็อกนี้จะถูกดำเนินการในทุกกรณี ไม่ว่าจะมีข้อยกเว้นเกิดขึ้นหรือไม่ก็ตาม ใน ส่วน นี้ของบทความในคำถามที่ 104 จะมีการกล่าวถึงสถานการณ์พิเศษที่บล็อกนี้จะไม่ถูกดำเนินการ Finalize()เป็นวิธีการของ คลาส Objectที่ถูกเรียกก่อนที่แต่ละ object จะถูกลบโดยตัวรวบรวมขยะ วิธีการนี้จะถูกเรียก (สุดท้าย) และใช้เพื่อล้างทรัพยากรที่ถูกครอบครอง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการของ คลาส Objectที่ทุก Object สืบทอด ดูคำถามที่ 11 ใน ส่วนนี้ ของบทความ นั่นคือสิ่งที่เราจะสิ้นสุดในวันนี้ พบกันใหม่ในตอนต่อไป! Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 14 - 9
วัสดุอื่นๆ ในชุด:
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION