[Java – S3] Biến

Xin chào các bạn, lại gặp lại mọi người trong số thứ 3 của chuyên mục Lập trình Java. Trong phần này, mình và các bạn  sẽ cùng tìm hiểu về các loại biến, cách khai báo cũng như cách sử dụng chúng như thế nào. Nội dung chính bao gồm:

  • Biến là gì?
  • Phân loại biến
  • Khai báo vào khởi tạo biến
  • Literals
  • Constant (hằng số)

Biến là gì?

Như ta đã biết, biến là một địa chỉ lưu trữ, được liên kết với một kiểu dữ liệu, nguyên thủy hoặc tham chiếu. Các biến nguyên thủy (primitive variable) lưu giá trị thực trên stack, trong khi đó các biến tham chiếu lưu địa chỉ  của đối tượng trong bộ nhớ heap mà nó tham chiếu đến. Vì vậy, các biến nguyên thủy thường được gán giá trị mặc định của kiểu dữ liệu khi khai báo không khởi tạo. Các biến tham chiếu nếu không được khởi tạo, sẽ không tham chiếu tới đối tượng nào trong bộ  nhớ và nhận giá trị null.

su-khac-nhau-giua-kieu-du-lieu-ngyen-thuy-va-kieu-du-lieu-tham-chieu-trong-java
Hình 3.1 Sự khác nhau giữa kiểu dữ liệu nguyên thủy và kiểu dữ liệu tham chiếu trong Java

Biến tham chiếu được ví như chiếc điều khiển từ xa, có khả năng truy cập từ bên ngoài (class, package khác) đến các phương thức hay các biến instance thông qua toán tử ‘.’ (tất nhiên còn phụ thuộc vào access modifier của các phương thức hoặc biến đó).

Mảng (tập hợp các phần tử cùng kiểu dữ liệu) là một biến tham chiếu (một đối tượng), mặc dù nó có thể được khai báo là chứa các phần tử nguyên thủy.

Phân loại biến

Ngôn ngữ lập trình Java định nghĩa 4 loại biến:

  • Biến Instance (Non-Static Fields)
    • Là các biến không chứa từ khóa static, được khai báo bên trong class nhưng bên ngoài các phương thức
    • Các biến instance có thể truy xuất được mọi nơi trong class (trừ các hàm static), và những nơi bên ngoài class mà kiểu access modifier của biến đó cho phép
  • Biến Class (Static Fields)
    • Là các biến có chứa từ khóa static, được khai báo bên trong class nhưng bên ngoài các phương thức
    • Các biến static được cấp phát bộ nhớ một lần duy nhất khi class được load. Một class được load bởi khối ClassLoader trong máy ảo Java (JVM) khi một trường hợp cụ thể (một object, một instance) của class đó được khởi tạo, hay có một truy xuất đến các biến (hàm) static của class đó
    • Có giá trị không phụ thuộc vào số đối tượng được tạo ra (có giá trị như nhau đối với các đối tượng khác nhau)
    • Bộ nhớ cấp phát cho các biến static chỉ được giải phóng khi kết thúc chương trình. Vì vậy, các biến static nên được hạn chế sử dụng để tránh memory leak
    • Có khả năng truy cập các biến bên ngoài class mà không cần khởi tạo một đối tượng của class đó

    Biến instance và biến class đại diện cho các trường trong class. Các biến class đại diện cho các trường tĩnh, không thể thay đổi được (về mặt đối tượng, không phải về mặt giá trị), luôn đi theo class nên được gọi là biến class. Các biến instance  đại diện cho các trường có thể thay đổi được, các thể hiện khác nhau của class (các object, các instance) thì giá trị của biến instance (thuộc tính) khác nhau, nên được gọi là biến instance.

  • Biến Local
    • Biến local là các biến non-static bên trong một phương thức hay một khối (không thể khai báo một biến trong một phương thức hay một khối)
    • Các biến local không thể truy xuất trực tiếp từ bên ngoài phương thức mà nó được khai báo (nếu muốn truy xuất giá trị của biến local, ta có thể truy xuất gián tiếp thong qua giá trị trả về của phương thúc đó)
    • Bộ nhớ cấp phát cho các biến local được giải phóng ngay khi phương thức kết thúc
    • Các biến local không được chỉ định giá trị mặc định. Vì vậy ta phải gán cho biến local một giá trị trước khi sử dung
  • Tham số (Parameters)
    • Nếu như khái niệm tham số trong phần Java – S1 chưa làm các bạn clear, hãy hiểu đơn giản tham số (nếu có) là một hay một vài biến đi kèm theo phương thức và luôn phải truyền vào cho nó một giá trị (cùng kiểu dữ liệu) khi phương thức được gọi đến
    • Giá trị truyền vào đó gọi là đối số. Trong nhiều trường hợp, đối số có thể là một literal, một hang số (xem bên dưới), giá trị của một biến, thậm chí là giá trị trả về của một hàm khác. Xin nhắc lại, trong trường hợp sử biến, cái truyền vào là giá trị của biến đó chứ không phải ô nhớ của biến đó, vì thế giá trị của biến truyền vào không bị thay đổi

Khai báo và khởi tạo biến

Cú pháp:

        access_modifier static (đối với các biến class) data_type variable_name; // khai báo không khởi tạo
hoặc access_modifier static (đối với các biến class) data_type variable_name = value; // khai báo đồng thời khởi tạo

  • access_modifier(public, protected, hoặc private) là các mức truy cập, đại diện cho khả năng truy xuất đến biến. Nếu không điền gì thì mức truy cập là default
  • static: Từ khóa được thêm vào các biến static, không thêm vào biến instance và biến local.
  • data_type: Kiểu dữ liệu nguyên thủy hoặc tham chiếu
  • variable_name: tên biến, được đặt theo các quy ước đặt tên của Java, không trùng với các từ khóa
  • = : Toán tử gán, chỉ định giá trị bên phải cho biến ở bên trái
  • value Một giá trị xác định, đại diện cho kiểu dữ liệu của biến, có thể là một literal, một contants hoặc một đối tượng…
  •  Ký tự kết thúc câu lệnh

public static int age = 15;
private String mColor = "blue"; // private String mColor = new String ("color"); is OK
Dog bitch = new Dog();
...

Literals

Trong ví dụ trên, chúng ta có thể thấy các biến tham chiếu có thể sử dụng từ khóa new để khởi tạo. Tuy nhiên điều này không được áp dụng với các biến kiểu nguyên thủy. Chúng ta có thể truyền trực tiếp một giá trị cố định, không yêu cầu tính toán, đại diện cho một kiểu dữ liệu, cho các biến này. Các giá trị đó, người ta gọi là Literals.

int num1 = 4; // 4 is a literal
String str = "Hello World"; // "Hello World" is s literal.
int num2 = 3 * 5; // 3*5 is not literal.(1)

Hằng số (Constant)

Hằng là một đại lượng có giá trị không thể thay đổi trong một vòng đời của chương trình. Để khai báo hằng, người ra thêm vào khai báo biến từ khóa final static. Vì vậy, hằng có tất cả các tính chất của một biến static. Các hằng số hay dùng thường được khai báo chung trong một  lớp Constants, và được truy xuất khi nào cần thiết như một biến static.

Ngoài ra, từ khóa final cũng có thể thêm vào các biến instance, biến local, thậm chí là các tham số, với ý nghĩa “không thay đổi giá trị được” (điều này có vẻ hơi mâu thuẫn với khái niệm biến nhưng nhiều khi người ta vẫn coi các biến final như một biến). Trong phần hướng đối tượng trong Java, chúng ra còn sử dụng final cho phương thức (với ý nghĩa không thể ghi đè phương thức đó) và class (với ý nghĩa không thể kế thừa class).


public static final String INPUT = "This is s String constant";

[Java – S2] Các kiểu dữ liệu trong Java

Như ở post Java-S1, chúng ta đã biết mỗi ngôn ngữ lập trình đều có nhiều kiểu dữ liệu. Trong Java có 2 kiểu dữ liệu cơ bản: Kiểu dữ liệu nguyên thủy (Primitive data type) và kiểu dữ liệu tham chiếu (Reference/Object data type). Mỗi kiểu dữ liệu này lại được chia ra các kiểu dữ liệu khác nhau:

cac-kieu-du-lieu-trong-java

8 kiểu dữ liệu nguyên thủy (cơ sở):

Các kiểu dữ liệu nguyên thủy trong java được phân ra thành 3 nhóm chính:

  • Kiểu số nguyên bao gồm: byte, short, int, long  và char
  • Kiểu số thực dấu phẩy động bao gồm: double và float
  • Kiểu logic chỉ có 2 giá trị: true, false

Chi tiết về các kiểu được trình bày ở bảng dưới đây:

Bảng 2.1: Chi tiết thông số của các kiểu dữ liệu nguyên thủy trong Java
Primitive Data Type Wrapper Type Size Default Value Range
byte Byte 1 byte 0 -128  to  127
short Short 2 byte 0 -215  to  215-1
int Integer 4 byte 0  -23  to  231-1
long Long 8 byte 0L -263  to  263-1
float Float 4 byte 0.0f (2)
double Double 8 byte 0.0d
char Character 2 byte ‘\u0000’ 0 to 215-1 (3)
boolean Boolean Not defined(1) false true , false

Bảng trên cho ta giới hạn của các kiểu dữ liệu nguyên thủy. Các thông số này đều có thể kiểm tra được bằng cách truy xuất các hằng số trong các lớp Wrapper type. Tuy nhiên, chúng ta nên nhớ các thông số này để có thể sử dụng chúng một cách hợp lý và linh hoạt. Ví dụ nếu không nhớ giới hạn của kiểu dữ liệu, bạn cũng có thể gán cho biến một giá trị vượt quá giới hạn (giống như việc nhét một con voi vào trong tủ lạnh vậy), hoặc sau hàng loạt các tính toán giá trị của biến sẽ vượt quá giới hạn…

Wrapper type không phải là kiểu dữ liệu nguyên thủy, nó là kiểu dữ liệu tham chiếu, là một class. Các class này là đóng gói của các kiểu dữ liệu nguyên thủy. Nói cách khác, các phương thức, các hằng và các biến được thêm vào để thao tác với các kiểu dữ liệu nguyên thủy được dễ hơn (như việc truy xuất các thông số ở trên). Các wrapper class được tìm thấy trong Java API.

	int i = Integer.MAX_VALUE; // 2147483647
	byte b = Byte.MIN_VALUE; // -128
	int c1 = Character.MAX_VALUE; // 65535
	char c2 = Character.MAX_VALUE; // ?
	short s = Short.MIN_VALUE; // -32768
	double d = Double.MAX_VALUE; // 1.7976931348623157E308
	

==============================

(1) Kiểu dữ liệu boolean trong Java chỉ nhận 2 giá trị true hoặc false, kích thước của nó là một cái gì đó được xác định chính xác. Tuy nhiên, vì nó yêu cầu ít nhất 1 bit, nên nhiều tài liệu lấy đó làm kích thước của kiểu boolean.
(2) float và double là các kiểu số thực dấu chấm động (floating-point type) tuân theo chuẩn IEEE 754. Số dấu chấm động không có phạm vi xác định, nó có thể nhận một trong các giá trị:

  • Vô cực âm(Negative infinity)
  • Vô cực dương (Positive infinity)
  • Số âm hữu hạn ( Negative, finite values)
  • Số dương hữu hạn (Positive, finite values)
  • Số 0 âm hoặc số 0 dương: Xem thêm tại đây.
  • NaN (Not a Number): Các giá trị không thể xác định trong khoảng vô cực âm đến vô cực dương (ví dụ phép toán chi cho 0). NaN được đưa ra để sử dụng trong một số trường hợp đặc biệt khi so sánh các giá trị thực với nhau và thường cho ra kết quả false.

(3) char là kiểu ký tự có giá trị trong khoảng ‘/u0000’ đến ‘/uffff’ hay từ 0 đến 65 535. Một biến kiểu char sẽ có giá trị là một ký tự Unicode.

Các kiểu dữ liệu tham chiếu
Kiểu dữ liệu tham chiếu là một class, một mảng, một interface hay một enum bất kì. Kiểu dữ liệu tham chiếu có kích thước phụ thuộc vào nhiều yếu tố đặc biệt kiến trúc của máy ảo Java (JVM).