1. การเริ่มต้นตัวแปร

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

และตัวแปรเดียวกันนี้สามารถเริ่มต้นในตัวสร้างได้เช่นกัน ซึ่งหมายความว่าตามทฤษฎีแล้ว ตัวแปรเหล่านี้สามารถกำหนดค่าได้สองครั้ง ตัวอย่าง

รหัส บันทึก
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



ตัวแปรageถูกกำหนดค่าเริ่มต้น




ค่าเริ่มต้นถูกเขียนทับ


ตัวแปรอายุเก็บค่าเริ่มต้น
Cat cat = new Cat("Whiskers", 2);
สิ่งนี้ได้รับอนุญาต: คอนสตรัคเตอร์แรกจะถูกเรียก
Cat cat = new Cat();
สิ่งนี้ได้รับอนุญาต: ตัวสร้างที่สองจะถูกเรียก

นี่คือสิ่งที่เกิดขึ้นเมื่อCat cat = new Cat("Whiskers", 2);ดำเนินการ:

  • Catมีการสร้างวัตถุ
  • ตัวแปรอินสแตนซ์ทั้งหมดจะเริ่มต้นด้วยค่าเริ่มต้น
  • คอนสตรัคเตอร์ถูกเรียกและรันโค้ด

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


2. ลำดับการเริ่มต้นของตัวแปรในคลาส

ตัวแปรไม่ได้ถูกเริ่มต้นก่อนที่คอนสตรัคเตอร์จะทำงานเท่านั้น แต่จะถูกเตรียมใช้งานตามลำดับที่กำหนดไว้อย่างดี: ลำดับที่ตัวแปรถูกประกาศในคลาส

ลองดูโค้ดที่น่าสนใจ:

รหัส บันทึก
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

รหัสนี้จะไม่คอมไพล์เนื่องจากในขณะที่aสร้างตัวแปรยังไม่มีb และc ตัวแปร แต่คุณสามารถเขียนโค้ดได้ดังนี้ — โค้ดนี้จะคอมไพล์และทำงานได้ดี

รหัส บันทึก
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

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

ที่นี่เราต้องจำไว้ว่าก่อนที่จะกำหนดค่าตัวแปรจะมีค่าเริ่มต้น สำหรับintประเภทนี้ ค่านี้เป็นศูนย์

เมื่อ JVM เริ่มต้นaตัวแปร มันจะกำหนดค่าเริ่มต้นสำหรับประเภท int: 0

เมื่อถึงbตัวแปร a จะเป็นที่รู้จักและมีค่าอยู่แล้ว ดังนั้น JVM จะกำหนดค่าให้เป็น 2

และเมื่อถึงcตัวแปร ตัวแปรaและbจะถูกเตรียมใช้งาน ดังนั้น JVM จะคำนวณค่าเริ่มต้นสำหรับc: 0+2+3 ได้ อย่างง่ายดาย

หากคุณสร้างตัวแปรภายในเมธอด คุณจะไม่สามารถใช้มันได้เว้นแต่คุณจะกำหนดค่าให้กับมันก่อนหน้านี้ แต่นี่ไม่เป็นความจริงสำหรับตัวแปรของคลาส! ถ้าไม่ได้กำหนดค่าเริ่มต้นให้กับตัวแปรของคลาส ก็จะมีการกำหนดค่าเริ่มต้น


3. ค่าคงที่

ในขณะที่เรากำลังวิเคราะห์วิธีการสร้างออบเจกต์ มันก็คุ้มค่าที่จะพิจารณาการกำหนดค่าเริ่มต้นของค่าคงที่ เช่น ตัวแปรที่มีตัวfinalปรับแต่ง

หากตัวแปรมีfinalตัวแก้ไข จะต้องกำหนดค่าเริ่มต้น คุณรู้เรื่องนี้แล้วและไม่มีอะไรน่าประหลาดใจเกี่ยวกับเรื่องนี้

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

ตัวอย่าง:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. รหัสในตัวสร้าง

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

ตัวอย่างเช่น ต่อไปนี้เป็นข้อสังเกตที่สำคัญเกี่ยวกับคอนสตรัคเตอร์ ในทางทฤษฎี คุณสามารถเขียนโค้ดที่มีความซับซ้อนเท่าใดก็ได้ในตัวสร้าง แต่อย่าทำแบบนี้ ตัวอย่าง:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






เปิดสตรีมอ่านไฟล์
อ่านไฟล์เป็นอาร์เรย์ไบต์
บันทึกอาร์เรย์ไบต์เป็นสตริง




แสดงเนื้อหาของไฟล์บนหน้าจอ

ในตัวสร้างคลาส FilePrinter เราเปิดสตรีมไบต์บนไฟล์ทันทีและอ่านเนื้อหา นี่เป็นลักษณะการทำงานที่ซับซ้อนและอาจส่งผลให้เกิดข้อผิดพลาด

จะทำอย่างไรหากไม่มีไฟล์ดังกล่าว จะเกิดอะไรขึ้นหากมีปัญหาในการอ่านไฟล์ ถ้ามันใหญ่เกินไปล่ะ?

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

ตัวอย่างที่ 1 — การทำให้เป็นอนุกรม

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

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

ตัวอย่างที่ 2 — การเริ่มต้นฟิลด์ของคลาส

หากตัวสร้างคลาสของคุณสามารถโยนข้อยกเว้นที่ตรวจสอบได้ เช่น ทำเครื่องหมายด้วยคีย์เวิร์ด Throws คุณต้องตรวจจับข้อยกเว้นที่ระบุในเมธอดที่สร้างวัตถุของคุณ

แต่ถ้าไม่มีวิธีการดังกล่าวล่ะ? ตัวอย่าง:

รหัส  บันทึก
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
รหัสนี้จะไม่รวบรวม

ตัวFilePrinterสร้างคลาสสามารถโยนข้อยกเว้นที่ตรวจสอบได้ซึ่งหมายความว่าคุณไม่สามารถสร้างFilePrinterออบเจกต์โดยไม่ล้อมมันไว้ในบล็อก try-catch และบล็อก try-catch สามารถเขียนได้ในเมธอดเท่านั้น



5. ตัวสร้างคลาสพื้นฐาน

ในบทที่แล้ว เราได้กล่าวถึงมรดกเล็กน้อย น่าเสียดายที่การสนทนาแบบเต็มของเราเกี่ยวกับการสืบทอดและ OOP ถูกสงวนไว้สำหรับระดับที่ทุ่มเทให้กับ OOP และการสืบทอดตัวสร้างนั้นเกี่ยวข้องกับเราอยู่แล้ว

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

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

ชั้นเรียน

เราจะทราบลำดับที่ตัวแปรเริ่มต้นและเรียกตัวสร้างได้อย่างไร เริ่มต้นด้วยการเขียนโค้ดสำหรับสองคลาส คนหนึ่งจะได้รับมรดกอีกคนหนึ่ง:

รหัส บันทึก
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










คลาสChildClass สืบทอดParentClassคลาส

เราจำเป็นต้องกำหนดลำดับในการเริ่มต้นตัวแปรและเรียกคอนสตรัคเตอร์ การบันทึกจะช่วยให้เราทำสิ่งนี้ได้

การบันทึก

การบันทึกเป็นกระบวนการของการบันทึกการกระทำที่ดำเนินการโดยโปรแกรมขณะที่มันทำงาน โดยเขียนลงในคอนโซลหรือไฟล์

มันค่อนข้างง่ายที่จะระบุว่าคอนสตรัคเตอร์ถูกเรียก: ในเนื้อหาของคอนสตรัคเตอร์ ให้เขียนข้อความไปยังคอนโซล แต่คุณจะทราบได้อย่างไรว่าตัวแปรได้รับการเริ่มต้นแล้ว?

จริงๆ แล้ว มันก็ไม่ใช่เรื่องยากเช่นกัน: เขียนเมธอดพิเศษที่จะคืนค่าที่ใช้ในการเริ่มต้นตัวแปร และบันทึกการเริ่มต้น นี่คือลักษณะของรหัส:

รหัสสุดท้าย

public class Main
{
   public static void main(String[] args)
   {
      ChildClass obj = new ChildClass();
   }

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




สร้างChildClassวัตถุ


วิธีนี้เขียนข้อความที่ส่งผ่านไปยังคอนโซลและส่งกลับ





ประกาศParentClassคลาส

แสดงข้อความ และเริ่มต้นตัวแปรด้วย




เขียนข้อความว่าตัวสร้างถูกเรียก ละเว้นค่าส่งคืน


ประกาศChildClassคลาส

แสดงข้อความ และเริ่มต้นตัวแปรด้วย




เขียนข้อความว่าตัวสร้างถูกเรียก ละเว้นค่าส่งคืน

หากคุณรันโค้ดนี้ ข้อความจะแสดงบนหน้าจอดังนี้:

เอาต์พุตคอนโซลของเมธอดMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

คุณจึงมั่นใจได้เสมอว่าตัวแปรของคลาสได้รับการเริ่มต้นก่อนที่จะเรียกคอนสตรัคเตอร์ คลาสพื้นฐานได้รับการเริ่มต้นอย่างสมบูรณ์ก่อนที่จะเริ่มต้นคลาสที่สืบทอดมา