1. Khởi tạo biến

Như bạn đã biết, bạn có thể khai báo một số biến trong lớp của mình và không chỉ khai báo chúng mà còn khởi tạo ngay chúng với các giá trị ban đầu của chúng.

Và các biến tương tự này cũng có thể được khởi tạo trong hàm tạo. Điều này có nghĩa là, về mặt lý thuyết, các biến này có thể được gán giá trị hai lần. Ví dụ

Mã số Ghi chú
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";
   }
}



Biến ageđược gán một giá trị ban đầu




Giá trị ban đầu được ghi đè


Biến tuổi lưu trữ giá trị ban đầu của nó.
 Cat cat = new Cat("Whiskers", 2);
Điều này được cho phép: hàm tạo đầu tiên sẽ được gọi
 Cat cat = new Cat();
Điều này được cho phép: hàm tạo thứ hai sẽ được gọi

Đây là những gì xảy ra khi Cat cat = new Cat("Whiskers", 2);được thực thi:

  • Một Catđối tượng được tạo
  • Tất cả các biến thể hiện được khởi tạo với các giá trị ban đầu của chúng
  • Hàm tạo được gọi và mã của nó được thực thi.

Nói cách khác, các biến trước tiên nhận các giá trị ban đầu của chúng và chỉ sau đó mã của hàm tạo mới được thực thi.


2. Thứ tự khởi tạo các biến trong một lớp

Các biến không chỉ đơn thuần được khởi tạo trước khi hàm tạo chạy — chúng được khởi tạo theo một thứ tự được xác định rõ ràng: thứ tự mà chúng được khai báo trong lớp.

Hãy xem xét một số mã thú vị:

Mã số Ghi chú
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Mã này sẽ không biên dịch, vì tại thời điểm abiến được tạo, chưa có biến b và c . Nhưng bạn có thể viết mã của mình như sau — mã này sẽ biên dịch và sẽ chạy tốt.

Mã số Ghi chú
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Nhưng hãy nhớ rằng mã của bạn phải minh bạch đối với các nhà phát triển khác. Tốt hơn là không nên sử dụng các kỹ thuật như thế này, vì nó làm giảm khả năng đọc mã.

Ở đây chúng ta phải nhớ rằng trước khi các biến được gán một giá trị, chúng có một giá trị mặc định . Đối với intloại, đây là số không.

Khi JVM khởi tạo abiến, nó sẽ chỉ gán giá trị mặc định cho kiểu int: 0.

Khi nó đạt đến b, biến a đã được biết và có giá trị, vì vậy JVM sẽ gán cho nó giá trị 2.

Và khi đến cbiến, biến abđã được khởi tạo sẵn, vì vậy JVM sẽ dễ dàng tính toán giá trị ban đầu cho c: 0+2+3.

Nếu bạn tạo một biến bên trong một phương thức, bạn không thể sử dụng nó trừ khi bạn đã gán giá trị cho nó trước đó. Nhưng điều này không đúng với các biến của một lớp! Nếu một giá trị ban đầu không được gán cho một biến của một lớp, thì nó được gán một giá trị mặc định.


3. Hằng số

Trong khi chúng tôi đang phân tích cách các đối tượng được tạo ra, thì cũng đáng để chạm vào việc khởi tạo các hằng số, tức là các biến với công finalcụ sửa đổi.

Nếu một biến có finalcông cụ sửa đổi, thì phải được gán một giá trị ban đầu. Bạn đã biết điều này, và không có gì đáng ngạc nhiên về nó.

Nhưng điều bạn không biết là bạn không phải gán giá trị ban đầu ngay lập tức nếu bạn gán nó trong hàm tạo. Điều này sẽ hoạt động tốt cho một biến cuối cùng. Yêu cầu duy nhất là nếu bạn có nhiều hàm tạo, thì biến cuối cùng phải được gán một giá trị trong mỗi hàm tạo.

Ví dụ:

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. Mã trong hàm tạo

Và một vài lưu ý quan trọng hơn về các nhà xây dựng. Sau này, khi bạn tiếp tục học Java, bạn sẽ bắt gặp những thứ như kế thừa, tuần tự hóa, ngoại lệ, v.v. Tất cả chúng đều ảnh hưởng đến công việc của các hàm tạo ở các mức độ khác nhau. Bây giờ không có ý nghĩa gì khi đi sâu vào các chủ đề này, nhưng ít nhất chúng ta có nghĩa vụ phải chạm vào chúng.

Ví dụ, đây là một nhận xét quan trọng về hàm tạo. Về lý thuyết, bạn có thể viết mã với bất kỳ độ phức tạp nào trong hàm tạo. Nhưng đừng làm điều này. Ví dụ:

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);
   }
}






Mở luồng đọc tệp
Đọc tệp thành mảng byte
Lưu mảng byte dưới dạng chuỗi




Hiển thị nội dung tệp lên màn hình

Trong phương thức khởi tạo của lớp FilePrinter, chúng ta đã ngay lập tức mở một luồng byte trên một tệp và đọc nội dung của nó. Đây là hành vi phức tạp và có thể dẫn đến lỗi.

Nếu không có tập tin như vậy thì sao? Điều gì xảy ra nếu có vấn đề với việc đọc tệp? Nếu nó quá lớn thì sao?

Logic phức tạp ngụ ý xác suất xảy ra lỗi cao và điều đó có nghĩa là mã phải xử lý các ngoại lệ một cách chính xác.

Ví dụ 1 - Đánh số sê-ri

Trong một chương trình Java tiêu chuẩn, có rất nhiều tình huống mà bạn không phải là người tạo ra các đối tượng của lớp mình. Ví dụ: giả sử bạn quyết định gửi một đối tượng qua mạng: trong trường hợp này, chính máy Java sẽ chuyển đổi đối tượng của bạn thành một tập hợp byte, gửi nó và tạo lại đối tượng từ tập hợp byte.

Nhưng sau đó, giả sử tệp của bạn không tồn tại trên máy tính kia. Sẽ có một lỗi trong hàm tạo và không ai xử lý nó. Và điều đó hoàn toàn có khả năng khiến chương trình chấm dứt.

Ví dụ 2 — Khởi tạo các trường của một lớp

Nếu hàm tạo lớp của bạn có thể ném các ngoại lệ đã kiểm tra, tức là được đánh dấu bằng từ khóa ném, thì bạn phải nắm bắt các ngoại lệ đã chỉ định trong phương thức tạo đối tượng của bạn.

Nhưng nếu không có phương pháp đó thì sao? Ví dụ:

Mã số  Ghi chú
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Mã này sẽ không biên dịch.

Hàm FilePrintertạo của lớp có thể đưa ra một ngoại lệ được kiểm tra , điều đó có nghĩa là bạn không thể tạo một FilePrinterđối tượng mà không bao bọc nó trong một khối thử bắt. Và một khối try-catch chỉ có thể được viết bằng một phương thức



5. Hàm tạo của lớp cơ sở

Trong các bài học trước, chúng ta đã thảo luận một chút về thừa kế. Thật không may, cuộc thảo luận đầy đủ của chúng tôi về tính kế thừa và OOP được dành riêng cho cấp độ dành riêng cho OOP và tính kế thừa của các hàm tạo đã phù hợp với chúng tôi.

Nếu lớp của bạn kế thừa một lớp khác, một đối tượng của lớp cha sẽ được nhúng bên trong một đối tượng của lớp bạn. Hơn nữa, lớp cha có các biến riêng và các hàm tạo riêng.

Điều đó có nghĩa là điều rất quan trọng đối với bạn là phải biết và hiểu cách các biến được khởi tạo và các hàm tạo được gọi khi lớp của bạn có một lớp cha và bạn kế thừa các biến và phương thức của nó.

Các lớp học

Làm thế nào để chúng ta biết thứ tự các biến được khởi tạo và các hàm tạo được gọi? Hãy bắt đầu bằng cách viết mã cho hai lớp. Cái này sẽ kế thừa cái kia:

Mã số Ghi chú
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

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

   public ChildClass()
   {
   }
}










Lớp ChildClass kế thừa ParentClasslớp.

Chúng ta cần xác định thứ tự các biến được khởi tạo và các hàm tạo được gọi. Ghi nhật ký sẽ giúp chúng tôi làm điều này.

ghi nhật ký

Ghi nhật ký là quá trình ghi lại các hành động được thực hiện bởi một chương trình khi nó chạy, bằng cách ghi chúng vào bảng điều khiển hoặc một tệp.

Khá đơn giản để xác định rằng hàm tạo đã được gọi: trong phần thân của hàm tạo, viết một thông báo tới bàn điều khiển. Nhưng làm thế nào bạn có thể biết nếu một biến đã được khởi tạo?

Thật ra, điều này cũng không khó lắm: viết một phương thức đặc biệt sẽ trả về giá trị được sử dụng để khởi tạo biến và ghi lại quá trình khởi tạo. Đây là những gì mã có thể trông giống như:

mã cuối cùng

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");
   }
}




Tạo một ChildClassđối tượng


Phương thức này ghi văn bản đã truyền vào bàn điều khiển và cũng trả về nó.





Khai báo ParentClasslớp

Hiển thị văn bản và cũng khởi tạo các biến với nó.




Viết một thông báo rằng hàm tạo đã được gọi. Bỏ qua giá trị trả về.


Khai báo ChildClasslớp

Hiển thị văn bản và cũng khởi tạo các biến với nó.




Viết một thông báo rằng hàm tạo đã được gọi. Bỏ qua giá trị trả về.

Nếu bạn thực thi mã này, văn bản sẽ được hiển thị trên màn hình như sau:

Đầu ra bảng điều khiển của phương thứcMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Vì vậy, bạn luôn có thể tự mình đảm bảo rằng các biến của một lớp được khởi tạo trước khi hàm tạo được gọi. Một lớp cơ sở được khởi tạo đầy đủ trước khi khởi tạo lớp kế thừa.