Data Type

Duy Văn | 05/03/2025


Trong Java có các loại biến khác nhau, chúng được phân ra làm hai loại, loại nguyên thuỷ và loại không nguyên thuỷ:

  • Nguyên thuỷ: bao gồm byte, short, int, long, float, double, char, boolean
  • Không nguyên thuỷ: bao gồm String, Array, Class

Primitive Data Type

Nguyên thuỷ hiểu nôm na là không thể chia nhỏ hơn, nó là các kiểu dữ liệu cơ bản nhất trong Java, ví dụ như một String thì chứa nhiều char nên String không phải nguyên thuỷ, còn char thì chỉ chứa một ký tự nên nó là nguyên thuỷ

Data TypeDescription
bytelưu số nguyên từ -128 đến 127
shortlưu số nguyên từ -32768 đến 32767
intlưu số nguyên từ -2^31 đến 2^31-1
longlưu số nguyên từ -2^63 đến 2^63-1
floatlưu số thực, đủ tốt để lưu số có 6-7 chữ số thập phân
doublelưu số thực, đủ tốt để lưu số có 15 chữ số thập phân
booleanlưu giá trị true hoặc false
charlưu một ký tự ASCII

Numbers

Số của kiểu data nguyên thuỷ được chia vào làm số nguyên và số thực

Integer Types

Bytes

Byte là kiểu dữ liệu nguyên thuỷ chứa được các giá trị số nguyên từ -128 đến 127 nó có thể dùng thay int để tiết kiệm bộ nhớ trong tình huống phải chắc chắn giá trị của biến không vượt quá giới hạn

byte a = 100;
System.out.println(a);

Nếu chúng ta thử gán cho a giá trị lớn hơn 127 thì sẽ báo lỗi error: incompatible types: possible lossy conversion from int to byte, tức là, số chúng ta đưa vào ban đầu được mặc định là int (Java mặc định literals số là int trước khi thực hiện bất kỳ dạng ép kiểu nào), sau khi sử dụng cú pháp dùng bytethì mới được chuyển thành byte, nhưng do byte không thể lưu quá 127 nên bit dư ra sẽ bị mất, dẫn đến lỗi lossy conversion

Short

Tương tự, nhưng Short có một vùng giá trị lớn hơn, từ -32768 đến 32767

short a = 10000;
System.out.println(a);

Int

Int là kiểu dữ liệu nguyên thuỷ chứa được các giá trị số nguyên từ -2^31 đến 2^31-1. Đây cũng là kiểu giữ liệu mặc định dành cho các literal dạng số nguyên, miễn là không có dấu thập phân thì ban đầu được coi là int

int a = 1000000;
System.out.println(a);

Long

Long là kiểu dữ liệu nguyên thuỷ chứa được các giá trị số nguyên từ -2^63 đến 2^63-1. Vì giá trị ban đầu được Java ghi nhận khi truyền literal dạng số nguyên là int, khi gán các giá trị lớn hơn mức của int thì nên đặt L hoặc l ở cuối giá trị để Java hiểu rằng đó là long

long a = 1000000000L;
System.out.println(a);

Có một khái niệm là Widening vs Narrowing Conversions, tức là ép kiểu từ kiểu dữ liệu nhỏ hơn sang lớn hơn sẽ được thực hiện tự động khi cần thiết, nhưng từ lớn hơn sang nhỏ hơn (narrowing) thì cần thực hiện các khâu xử lý cũng như phải cast chay để đảm bảo không mất dữ liệu

Nếu khâu tính toán bị vượt mức giới hạn của dữ liệu, Java sẽ không báo về lỗi mà chỉ đơn giản là chạy ngược về phía giới hạn bên kia. Ví dụ, nếu chúng ta cộng 1 cho giá trị lớn nhất của int thì kết quả sẽ là giá trị nhỏ nhất của int

Floating Points

Các số thực sẽ chứa dấu thập phân, và tốt hơn hết là khi khai báo và gán giá trị cho các biến thuộc kiểu dữ liệu này nên có chữ cái “f” (cho float) hoặc “d” (cho double)

Float

Float là kiểu dữ liệu nguyên thuỷ chứa được các giá trị số thực, đủ tốt để lưu số có 6-7 chữ số thập phân

float a = 3.14f;
System.out.println(a);

Double

Double là kiểu dữ liệu nguyên thuỷ chứa được các giá trị số thực, đủ tốt để lưu số có 15 chữ số thập phân

double b = 3.1415926535897938d;
System.out.println(b);

Ngoài ra chúng ta có thể dùng “e” hay “E” để biểu diễn số mũ, ví dụ 1.2e3 tương đương với 1.2 * 10^3

Boolean

Boolean về cơ bản là một kiểu dữ liệu chỉ chứa giá trị true hoặc false, nó thường được sử dụng trong các biểu thức điều kiện

boolean a = true;
System.out.println(a);

Char

Char là kiểu dữ liệu nguyên thuỷ chứa một ký tự ASCII, nó được bao bọc bởi cặp dấu nháy đơn '

char a = 'a';
System.out.println(a);

Ngoài ra chúng ta có thể gán giá trị số tương ứng với ký tự ASCII, ví dụ char a = 65; sẽ tương đương với ký tự A

char a = 65;
System.out.println(a); // A

Mặc dù String mình sẽ viết ở bài sau nhưng cũng đáng nói đến vì String là kiểu dữ liệu dùng rất nhiều và được tích hợp cực mạnh trong Java, object string có các method để biến đổi, và String thì được cấu thành từ nhiều char, nhưng cũng có thể có những String chỉ có một ký tự

Non Primitive Data Type

Non-primitive còn có tên gọi khác là Reference Type, nó không chứa object có giá trị trực tiếp mà chỉ chứa địa chỉ của các object đó, nó bao gồm các kiểu dữ liệu như String, Array, Class

  • Primitive trong Java được định nghĩa từ đầu và xây dựng sẵn trong Java, còn Non-primitive thì được xây dựng từ các kiểu dữ liệu nguyên thuỷ và có thể được tạo ra từ các class khác, chỉ riêng String được xây dựng sẵn, còn Array và Class cần phải được tạo ra bởi lập trình viên
  • Non-rpimitive có thể được dùng để gọi ra method để thực hiện các tác vụ, còn Primitive thì không thể
  • Primitive được đánh dấu tên là viết thường toàn bộ, như int, float, boolean, còn Non-primitive thì viết hoa chữ cái đầu, như String, Array, Class
  • Primitive luôn có giá trị, còn Non-primitive thì chứa địa chỉ của object nên có thể null

Casting

Như mình đã đề cập ở trên thì có hai khái niệm là Widening Casting và Narrowing Casting

  • Widening casting sẽ được thực hiện tự động, do bản chất các data type dạng số có giới hạn lớn hơn đã bao gồm các data type có giới hạn nhỏ hơn ở trong, một số ở type byte cũng là thuộc short và int rồi. Thứ tự chuyển đổi : byte -> short -> char -> int -> long -> float -> double
  • Narrowing casting sẽ phải thực hiện bằng tay, do bản chất data khi đi từ các vùng giới hạn lớn về nhỏ sẽ đi từ mức khối lượng nhị phân cao xuống thấp, ví dụ ở int chúng ta có 32bit, short là 16bit, vậy thì khi chuyển từ int về short, nếu giá trị các bit có nghĩa vượt quá 16bit, sẽ gây mất dữ liệu: double -> float -> long -> int -> char -> short -> byte

Có hai vấn đề mà ta cần làm rõ:

  • Cú pháp để cast
  • Tại sao char lại chui vào giữa kia?

Cú pháp để cast

Widening casting sẽ được thực hiện tự động, ví dụ

int myInt = 9;
double myDouble = myInt * 2; // tự động chuyển myInt thành double
System.out.println(myDouble); // 18.0

Trong tình huống này thì ban đầu myInt là int, sau đó do một biến double tham gia vào phép toán nên int được tự đưa về double để thực hiện phép nhân

Nhưng ngược lại thì sao?

double myDouble = 9.78;
int myInt = myDouble*2; // error: possbile lossy conversion from double to int
System.out.println(myInt);

Để thực hiện được chuyển đổi ngược, chúng ta cần thực hiện Narrowing casting, cú pháp của nó là:

double myDouble = 9.78;
int myInt = (int) myDouble * 2;
System.out.println(myInt); // 18

Ở đây chúng ta có thể quan sát là 9.78 sau khi chuyển về int sẽ chỉ còn phần nguyên, hành vi này về cơ bản là giống phía Python

Nhưng tại sao char lại chui vào giữa kia?

Vì char là kiểu dữ liệu chứa một ký tự ASCII, nó có giá trị từ 0 đến 65535, nên nó có thể chứa được các giá trị của byte, short, int, long, float, double, nhưng ngược lại thì không, nếu chúng ta chuyển từ char sang int thì giá trị của char sẽ được chuyển sang mã ASCII tương ứng

char myChar = 'A';
int myInt = myChar;
System.out.println(myInt); // 65

Ngược lại, cần phải thực hiện Narrowing casting

int myInt = 65;
char myChar = (char) myInt;
System.out.println(myChar); // A