JavaRush /Blog Java /Random-VI /Chuyển nhượng và khởi tạo trong Java
Viacheslav
Mức độ

Chuyển nhượng và khởi tạo trong Java

Xuất bản trong nhóm

Giới thiệu

Mục đích chính của các chương trình máy tính là xử lý dữ liệu. Để xử lý dữ liệu bạn cần lưu trữ nó bằng cách nào đó. Tôi đề xuất hiểu cách lưu trữ dữ liệu.
Gán và khởi tạo trong Java - 1

Biến

Biến là nơi lưu trữ bất kỳ dữ liệu nào. Hãy xem Hướng dẫn chính thức từ Oracle: Khai báo các biến thành viên . Theo Hướng dẫn này, có một số loại biến:
  • Fields : các biến được khai báo trong lớp;
  • Biến cục bộ : biến trong một phương thức hoặc khối mã;
  • Parameters : các biến trong phần khai báo phương thức (trong chữ ký).
Tất cả các biến phải có loại biến và tên biến.
  • Kiểu của biến cho biết biến đó đại diện cho dữ liệu nào (nghĩa là biến đó có thể lưu trữ dữ liệu gì). Như chúng ta đã biết, kiểu của biến có thể là nguyên thủy (primitives ) hoặc object chứ không phải kiểu nguyên thủy (Non-primitive). Với các biến đối tượng, kiểu của chúng được mô tả bởi một lớp cụ thể.
  • Tên biến phải là chữ thường, viết hoa lạc đà. Bạn có thể đọc thêm về cách đặt tên trong " Variables:Naming ".
Ngoài ra, nếu một biến cấp độ lớp, tức là. là một trường lớp, một công cụ sửa đổi truy cập có thể được chỉ định cho nó. Xem Kiểm soát quyền truy cập của các thành viên trong lớp để biết thêm chi tiết .

Sự định nghĩa biến

Vì vậy, chúng ta nhớ biến là gì. Để bắt đầu làm việc với một biến, bạn cần khai báo nó. Đầu tiên, hãy xem xét một biến cục bộ. Thay vì IDE, để thuận tiện, chúng tôi sẽ sử dụng giải pháp trực tuyến từ tutorialspoint: Online IDE . Hãy chạy chương trình đơn giản này trong IDE trực tuyến của họ:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Vì vậy, như bạn có thể thấy, chúng tôi đã khai báo một biến cục bộ có tên numbervà kiểu int. Chúng tôi nhấn nút “Thực thi” và gặp lỗi:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
Chuyện gì đã xảy ra thế? Chúng ta đã khai báo một biến nhưng không khởi tạo giá trị của nó. Điều đáng lưu ý là lỗi này không xảy ra tại thời điểm thực thi (tức là không phải trong Thời gian chạy) mà là tại thời điểm biên dịch. Trình biên dịch thông minh đã kiểm tra xem biến cục bộ có được khởi tạo trước khi truy cập hay không. Do đó, các tuyên bố sau đây được rút ra từ điều này:
  • Các biến cục bộ chỉ nên được truy cập sau khi chúng được khởi tạo;
  • Biến cục bộ không có giá trị mặc định;
  • Các giá trị của biến cục bộ được kiểm tra tại thời điểm biên dịch.
Vì vậy, chúng tôi được thông báo rằng biến phải được khởi tạo. Khởi tạo một biến là gán giá trị cho một biến. Sau đó chúng ta hãy tìm hiểu nó là gì và tại sao.

Khởi tạo một biến cục bộ

Khởi tạo biến là một trong những chủ đề khó nhất trong Java, bởi vì... có liên quan rất chặt chẽ đến việc làm việc với bộ nhớ, việc triển khai JVM, đặc tả JVM và những thứ phức tạp và đáng sợ không kém khác. Nhưng bạn có thể cố gắng tìm ra nó ít nhất ở một mức độ nào đó. Hãy đi từ đơn giản đến phức tạp. Để khởi tạo biến, chúng ta sẽ sử dụng toán tử gán và thay đổi dòng trong mã trước đó:
int number = 2;
Ở tùy chọn này sẽ không có lỗi và giá trị sẽ được hiển thị trên màn hình. Điều gì xảy ra trong trường hợp này? Hãy thử lý luận. Nếu chúng ta muốn gán một giá trị cho một biến thì chúng ta muốn biến đó lưu trữ một giá trị. Hóa ra giá trị phải được lưu trữ ở đâu đó, nhưng ở đâu? Trên đĩa? Nhưng điều này rất chậm và có thể áp đặt những hạn chế đối với chúng tôi. Hóa ra nơi duy nhất mà chúng ta có thể lưu trữ dữ liệu “tại đây và bây giờ” một cách nhanh chóng và hiệu quả chính là bộ nhớ. Điều này có nghĩa là chúng ta cần phân bổ một số không gian trong bộ nhớ. Điều này là đúng. Khi một biến được khởi tạo, không gian sẽ được phân bổ cho biến đó trong bộ nhớ được cấp phát cho tiến trình java nơi chương trình của chúng ta sẽ được thực thi. Bộ nhớ được phân bổ cho một tiến trình java được chia thành nhiều vùng hoặc vùng. Việc nào trong số chúng sẽ phân bổ không gian tùy thuộc vào loại biến được khai báo. Bộ nhớ được chia thành các phần sau: Heap, Stack và Non-Heap . Hãy bắt đầu với bộ nhớ ngăn xếp. Stack được dịch là một chồng (ví dụ: chồng sách). Đó là cấu trúc dữ liệu LIFO (Vào sau, ra trước). Đó là, giống như một chồng sách. Khi chúng tôi thêm sách vào đó, chúng tôi đặt chúng lên trên và khi chúng tôi lấy chúng đi, chúng tôi lấy sách trên cùng (tức là sách được thêm gần đây nhất). Vì vậy, chúng tôi khởi động chương trình của chúng tôi. Như chúng ta đã biết, một chương trình Java được thực thi bởi một JVM, tức là một máy ảo Java. JVM phải biết việc thực thi chương trình sẽ bắt đầu từ đâu. Để làm điều này, chúng ta khai báo một phương thức chính, được gọi là “điểm vào”. Để thực thi trong JVM, một luồng chính (Thread) được tạo. Khi một luồng được tạo, nó sẽ được cấp phát ngăn xếp riêng trong bộ nhớ. Ngăn xếp này bao gồm các khung. Khi mỗi phương thức mới được thực thi trong một luồng, một khung mới sẽ được phân bổ cho nó và được thêm vào đầu ngăn xếp (giống như một cuốn sách mới trong một chồng sách). Khung này sẽ chứa các tham chiếu đến các đối tượng và kiểu nguyên thủy. Có, vâng, int của chúng ta sẽ được lưu trữ trên ngăn xếp, bởi vì... int là kiểu nguyên thủy. Trước khi phân bổ một khung, JVM phải hiểu những gì cần lưu ở đó. Chính vì lý do này mà chúng ta sẽ nhận được lỗi “biến có thể chưa được khởi tạo”, vì nếu nó không được khởi tạo thì JVM sẽ không thể chuẩn bị ngăn xếp cho chúng ta. Vì vậy, khi biên dịch một chương trình, một trình biên dịch thông minh sẽ giúp chúng ta tránh mắc lỗi, hỏng hóc mọi thứ. (!) Để rõ ràng, tôi đề xuất một bài viết siêu lừa đảo : “ Java Stack and Heap: Java Memory Allocation Tutorial ”. Nó liên kết đến một video thú vị không kém:
Sau khi phương thức được hoàn thành, các khung được phân bổ cho các phương thức này sẽ bị xóa khỏi ngăn xếp của luồng và cùng với chúng, bộ nhớ được phân bổ cho khung này cùng với tất cả dữ liệu sẽ bị xóa.

Khởi tạo các biến đối tượng cục bộ

Hãy thay đổi mã của chúng ta một lần nữa để phức tạp hơn một chút:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
Điều gì sẽ xảy ra ở đây? Hãy nói về nó một lần nữa. JVM biết nó sẽ thực thi chương trình từ đâu, tức là. cô ấy nhìn thấy phương pháp chính. Nó tạo ra một luồng và phân bổ bộ nhớ cho nó (xét cho cùng, một luồng cần lưu trữ dữ liệu cần thiết để thực thi ở đâu đó). Trong luồng này, một khung được phân bổ cho phương thức chính. Tiếp theo chúng ta tạo một đối tượng HelloWorld. Đối tượng này không còn được tạo trên ngăn xếp nữa mà trên heap. Bởi vì đối tượng không phải là kiểu nguyên thủy mà là kiểu đối tượng. Và ngăn xếp sẽ chỉ lưu trữ một tham chiếu đến đối tượng trong heap (bằng cách nào đó chúng ta phải truy cập vào đối tượng này). Tiếp theo, trong ngăn xếp của phương thức chính, các khung sẽ được phân bổ để thực thi phương thức println. Sau khi thực hiện phương thức chính, tất cả các khung sẽ bị hủy. Nếu khung bị hủy, tất cả dữ liệu sẽ bị hủy. Đối tượng đối tượng sẽ không bị phá hủy ngay lập tức. Đầu tiên, tham chiếu đến nó sẽ bị hủy và do đó sẽ không ai tham chiếu đến đối tượng đối tượng nữa và việc truy cập vào đối tượng này trong bộ nhớ sẽ không còn khả dụng nữa. Một JVM thông minh có cơ chế riêng cho việc này - trình thu gom rác (gọi tắt là trình thu gom rác hoặc GC). Sau đó, nó sẽ xóa khỏi bộ nhớ các đối tượng mà không ai khác tham chiếu. Quá trình này một lần nữa được mô tả trong liên kết ở trên. Thậm chí còn có một video với lời giải thích.

Đang khởi tạo các trường

Việc khởi tạo các trường được chỉ định trong một lớp diễn ra theo cách đặc biệt tùy thuộc vào trường đó có tĩnh hay không. Nếu một trường có từ khóa tĩnh thì trường này đề cập đến chính lớp đó và nếu từ tĩnh không được chỉ định thì trường này đề cập đến một phiên bản của lớp. Hãy xem xét điều này với một ví dụ:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
Trong ví dụ này, các trường được khởi tạo vào các thời điểm khác nhau. Trường số sẽ được khởi tạo sau khi đối tượng lớp HelloWorld được tạo. Nhưng trường đếm sẽ được khởi tạo khi lớp được máy ảo Java tải. Tải lớp là một chủ đề riêng biệt nên chúng tôi sẽ không gộp nó vào đây. Điều đáng biết là các biến tĩnh được khởi tạo khi lớp được biết đến trong thời gian chạy. Ở đây còn có điều gì đó quan trọng hơn và bạn đã nhận thấy điều này. Chúng tôi không chỉ định giá trị ở bất cứ đâu nhưng nó vẫn hoạt động. Và thực sự. Các biến là các trường, nếu chúng không có giá trị được chỉ định, chúng sẽ được khởi tạo với giá trị mặc định. Đối với các giá trị số, đây là 0 hoặc 0,0 đối với số dấu phẩy động. Đối với boolean điều này là sai. Và đối với tất cả các biến loại đối tượng, giá trị sẽ là null (chúng ta sẽ nói về điều này sau). Có vẻ như tại sao lại như vậy? Nhưng vì các đối tượng được tạo trong Heap (trong heap). Làm việc với khu vực này được thực hiện trong Runtime. Và chúng ta có thể khởi tạo các biến này trong thời gian chạy, không giống như ngăn xếp, bộ nhớ phải được chuẩn bị trước khi thực thi. Đây là cách bộ nhớ hoạt động trong Java. Nhưng có một tính năng nữa ở đây. Mảnh nhỏ này chạm đến những góc khác nhau của ký ức. Như chúng ta nhớ, một khung được phân bổ trong bộ nhớ Stack cho phương thức chính. Khung này lưu trữ một tham chiếu đến một đối tượng trong bộ nhớ Heap. Nhưng số lượng được lưu trữ ở đâu? Như chúng ta nhớ, biến này được khởi tạo ngay lập tức, trước khi đối tượng được tạo trong heap. Đây thực sự là một câu hỏi khó. Trước Java 8, có một vùng bộ nhớ tên là PERMGEN. Bắt đầu từ Java 8, khu vực này đã thay đổi và được gọi là METASPACE. Về cơ bản, các biến tĩnh là một phần của định nghĩa lớp, tức là. siêu dữ liệu của nó. Do đó, điều hợp lý là nó được lưu trữ trong kho siêu dữ liệu METASPACE. MetaSpace thuộc cùng vùng bộ nhớ Non-Heap và là một phần của nó. Điều quan trọng cần lưu ý là thứ tự khai báo các biến cũng được tính đến. Ví dụ: có một lỗi trong mã này:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

null là gì

Như đã nêu ở trên, các biến của kiểu đối tượng, nếu chúng là các trường của một lớp, được khởi tạo về giá trị mặc định và giá trị mặc định đó là null. Nhưng null trong Java là gì? Điều đầu tiên cần nhớ là các kiểu nguyên thủy không thể rỗng. Và tất cả bởi vì null là một tham chiếu đặc biệt không đề cập đến bất kỳ đâu, đến bất kỳ đối tượng nào. Do đó, chỉ một biến đối tượng có thể rỗng. Điều quan trọng thứ hai cần hiểu là null là một tham chiếu. Tôi tham khảo cũng có trọng lượng của họ. Về chủ đề này, bạn có thể đọc câu hỏi trên stackoverflow: " Biến null có yêu cầu dung lượng trong bộ nhớ không ".

Khối khởi tạo

Khi xem xét việc khởi tạo các biến, sẽ thật tội lỗi nếu không xem xét các khối khởi tạo. Nó trông như thế này:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

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

}
Thứ tự đầu ra sẽ là: khối tĩnh, khối, Trình xây dựng. Như chúng ta có thể thấy, các khối khởi tạo được thực thi trước hàm tạo. Và đôi khi đây có thể là một phương tiện khởi tạo thuận tiện.

Phần kết luận

Tôi hy vọng phần tổng quan ngắn này có thể cung cấp một số hiểu biết sâu sắc về cách thức hoạt động và lý do. #Viacheslav
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION