1. คุณสมบัติ: getters และ setters

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

บางทีผู้คนอาจไม่ได้ศึกษาเอกสารประกอบของชั้นเรียนโดยละเอียด หรือบางทีอาจไม่ได้อธิบายถึงทุกกรณี เป็นผลให้มีสถานการณ์บ่อยครั้งที่ข้อมูลภายในของออบเจ็กต์อาจ "เสียหาย" ทำให้ออบเจ็กต์ไม่ถูกต้อง

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

หากคุณต้องการให้คลาสอื่นสามารถรับหรือเปลี่ยนแปลงข้อมูลภายในออบเจกต์ของคลาสของคุณได้ คุณต้องเพิ่มเมธอดสองเมธในคลาสของคุณ — เมธอด get และเมธอด set ตัวอย่าง:

รหัส บันทึก
class Person
{
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }
}


privateฟิลด์ชื่อ



การเริ่มต้นของฟิลด์ผ่านตัวสร้าง


getName()— วิธีนี้จะส่งกลับค่าของฟิลด์ชื่อ




setName()— วิธีนี้จะเปลี่ยนค่าของฟิลด์ชื่อ

ไม่มีคลาสอื่นใดที่สามารถเปลี่ยนค่าของฟิลด์ชื่อได้โดยตรง หากมีคนต้องการรับค่าของฟิลด์ชื่อ พวกเขาจะต้องเรียกใช้เมธอดgetName() บนPersonวัตถุ ถ้าบางโค้ดต้องการเปลี่ยนค่าของฟิลด์ชื่อ จะต้องเรียกเมธอดsetName() บนPersonวัตถุ

เมธอด นี้getName()เรียกอีกอย่างว่า " getterสำหรับฟิลด์ชื่อ" และsetName()เมธอดนี้เรียกว่า " setterสำหรับฟิลด์ชื่อ"

นี่เป็นวิธีการทั่วไป ใน 80-90% ของโค้ด Java ทั้งหมด คุณจะไม่เห็นตัวแปรสาธารณะในคลาส แต่จะถูกประกาศprivate(หรือprotected) แทน และแต่ละตัวแปรจะมีตัวรับและตัวตั้งสาธารณะ

วิธีการนี้ทำให้โค้ดยาวขึ้นแต่มีความน่าเชื่อถือมากขึ้น

การเข้าถึงคลาสตัวแปรโดยตรงก็เหมือนกับการเปลี่ยนรถของคุณผ่านเส้นสีเหลืองสองเส้นมันง่ายกว่าและเร็วกว่า แต่ถ้าทุกคนทำอย่างนั้น ทุกอย่างก็จะแย่ลงสำหรับทุกคน

สมมติว่าคุณต้องการสร้างคลาสที่อธิบายจุด ( x, y) นี่คือวิธีที่โปรแกรมเมอร์มือใหม่จะทำ:

class Point
{
   public int x;
   public int y;
}

นี่คือวิธีที่โปรแกรมเมอร์ Java ที่มีประสบการณ์จะทำ:

รหัส
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y;
   }
}

รหัสยาวขึ้นหรือไม่ อย่างไม่ต้องสงสัย

แต่คุณสามารถเพิ่มการตรวจสอบพารามิเตอร์ให้กับ getters และ setters ตัวอย่างเช่น คุณสามารถตรวจสอบให้แน่ใจว่าxและyมีค่ามากกว่าศูนย์เสมอ (หรือไม่น้อยกว่าศูนย์) ตัวอย่าง:

รหัส บันทึก
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x < 0 ? 0 : x;
      this.y = y < 0 ? 0 : y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x < 0 ?  0 : x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y < 0 ? 0 : y;
   }
}


2. อายุการใช้งานของวัตถุ

คุณรู้อยู่แล้วว่าวัตถุถูกสร้างขึ้นโดยใช้newตัวดำเนินการ แต่วัตถุจะถูกลบอย่างไร พวกมันไม่มีอยู่ตลอดไป มีหน่วยความจำไม่เพียงพอสำหรับสิ่งนั้น

ในภาษาการเขียนโปรแกรมหลายภาษา เช่น C++ มีdeleteตัวดำเนินการพิเศษสำหรับการลบวัตถุ แต่มันทำงานอย่างไรใน Java?

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

ใน Java การลบออบเจกต์จะเป็นไปโดยอัตโนมัติทั้งหมด เครื่อง Java เองจัดการการลบวัตถุ กระบวนการนี้เรียกว่าการรวบรวมขยะ และกลไกที่รวบรวมขยะเรียกว่าตัวรวบรวมขยะ ( GC )

ดังนั้นเครื่อง Java รู้ได้อย่างไรว่าเมื่อใดควรลบวัตถุ

ตัวรวบรวมขยะแบ่งวัตถุทั้งหมดเป็น "เข้าถึงได้" และ "เข้าถึงไม่ได้" หากมีการอ้างอิงถึงวัตถุอย่างน้อยหนึ่งรายการ จะถือว่าเข้าถึงได้ หากไม่มีตัวแปรที่อ้างอิงถึงวัตถุ จะถือว่าวัตถุนั้นไม่สามารถเข้าถึงได้และถูกประกาศว่าเป็นขยะ ซึ่งหมายความว่าวัตถุนั้นสามารถลบทิ้งได้

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

การอ้างอิงแบบวงกลม

ตรรกะนั้นฟังดูดีจนกระทั่งเราพบตัวอย่างง่ายๆ: สมมติว่าเรามีวัตถุสองชิ้นที่อ้างอิงถึงกันและกัน (เก็บข้อมูลอ้างอิงถึงกันและกัน) ไม่มีอ็อบเจกต์อื่นเก็บการอ้างอิงถึงอ็อบเจ็กต์เหล่านี้

ไม่สามารถเข้าถึงอ็อบเจ็กต์เหล่านี้ได้จากรหัส แต่ยังคงถูกอ้างอิง

นี่คือสาเหตุที่ตัวรวบรวมขยะแบ่งวัตถุออกเป็นที่เข้าถึงได้และเข้าถึงไม่ได้ แทนที่จะเป็น "อ้างอิง" และ "ไม่อ้างอิง"

วัตถุที่สามารถเข้าถึงได้

ประการแรก วัตถุที่มีชีวิต 100% จะถูกเพิ่มลงในรายการที่เข้าถึงได้ ตัวอย่างเช่น เธรดปัจจุบัน ( Thread.current()) หรือ Console InputStream ( System.in)

จากนั้นรายการของวัตถุที่สามารถเข้าถึงได้จะขยายเพื่อรวมวัตถุที่อ้างอิงโดยชุดเริ่มต้นของวัตถุที่สามารถเข้าถึงได้ จากนั้นจะขยายอีกครั้งเพื่อรวมวัตถุที่อ้างอิงโดยชุดที่ขยายนี้ และอื่นๆ

ซึ่งหมายความว่าหากมีวัตถุบางอย่างที่อ้างถึงกันเท่านั้น แต่ไม่มีวิธีเข้าถึงวัตถุเหล่านั้นจากวัตถุที่เข้าถึงได้ วัตถุเหล่านั้นจะถือว่าเป็นขยะและจะถูกลบ


3. การเก็บขยะ

การกระจายตัวของหน่วยความจำ

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

เป็นผลให้เราเข้าสู่สถานการณ์ที่เราไม่สามารถสร้างวัตถุขนาดใหญ่ได้อย่างง่ายดาย (เช่น อาร์เรย์ที่มีองค์ประกอบเป็นล้าน) เนื่องจากไม่มีหน่วยความจำว่างจำนวนมาก กล่าวอีกนัยหนึ่ง อาจมีหน่วยความจำว่าง แม้จะมีจำนวนมาก แต่อาจไม่มีบล็อกหน่วยความจำว่างขนาดใหญ่ที่อยู่ติดกัน

การเพิ่มประสิทธิภาพหน่วยความจำ (จัดเรียงข้อมูล)

เครื่อง Java แก้ปัญหานี้ด้วยวิธีเฉพาะ ดูเหมือนว่า:

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

กระบวนการมีลักษณะดังนี้:

ขั้นตอนที่ 1: หลังจากสร้างวัตถุ

การรวบรวมขยะใน Java

ขั้นตอนที่ 2: ลักษณะของ "รู"

การรวบรวมขยะใน Java 2

ขั้นตอนที่ 3: กำจัด "รู"

การรวบรวมขยะใน Java 3

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