Kiểu dữ liệu và Biến
Sau bài này, bạn sẽ:
- Phân biệt được 8 primitive types và reference types trong Java
- Hiểu sự khác nhau giữa cách lưu trữ primitive và reference trong memory (stack vs heap)
- Sử dụng final keyword cho constants
- Hiểu variable scope (local, instance, class) và lifetime của biến
Bài trước: Cú pháp cơ bản Java — Đã học về naming conventions và cấu trúc chương trình. Bài này sẽ tìm hiểu các kiểu dữ liệu và cách khai báo biến trong Java.
Java là strongly typed language - mọi variable phải có type rõ ràng. Bài học này sẽ giới thiệu các kiểu dữ liệu trong Java và cách làm việc với biến.
Kiểu dữ liệu trong Java
Java có 2 loại kiểu dữ liệu:
- Primitive types (8 types) - Lưu giá trị trực tiếp
- Reference types - Lưu địa chỉ (reference) của object
┌─────────────────────────────────┐
│ Java Data Types │
├────────────────┬────────────────┤
│ Primitive │ Reference │
├────────────────┼────────────────┤
│ - byte │ - Classes │
│ - short │ - Interfaces │
│ - int │ - Arrays │
│ - long │ - Enums │
│ - float │ │
│ - double │ │
│ - char │ │
│ - boolean │ │
└────────────────┴────────────────┘
Primitive Types (8 types)
Primitive types lưu trữ giá trị trực tiếp trong memory, nhanh và hiệu quả.
1. Integer Types (Số nguyên)
| Type | Size | Range | Default Value | Example |
|---|---|---|---|---|
byte | 1 byte (8 bits) | -128 to 127 | 0 | byte age = 25; |
short | 2 bytes (16 bits) | -32,768 to 32,767 | 0 | short year = 2024; |
int | 4 bytes (32 bits) | -2³¹ to 2³¹-1 (~-2B to 2B) | 0 | int population = 1000000; |
long | 8 bytes (64 bits) | -2⁶³ to 2⁶³-1 | 0L | long distance = 9460730472580800L; |
Two's Complement — Tại sao byte range là -128 to 127?
Java dùng two's complement representation cho tất cả integer types.
Two's complement là cách biểu diễn số nguyên có dấu (signed integers) trong binary.
Byte (8 bits) breakdown:
Positive numbers (0 to 127):
0000 0000 = 0
0000 0001 = 1
0000 0010 = 2
...
0111 1111 = 127 ← Maximum positive (bit đầu = 0)
Negative numbers (-128 to -1):
1000 0000 = -128 ← Minimum negative (bit đầu = 1)
1000 0001 = -127
1000 0010 = -126
...
1111 1110 = -2
1111 1111 = -1
- Bit đầu = 0: Số dương
- Bit đầu = 1: Số âm
Range của n-bit signed integer:
- Min: -2^(n-1)
- Max: 2^(n-1) - 1
Ví dụ byte (8 bits):
- Min: -2^7 = -128
- Max: 2^7 - 1 = 127
Tại sao -128 nhưng +127 (không đối xứng)?
8 bits có 2^8 = 256 giá trị:
- 1 giá trị cho 0
- 127 giá trị cho positive (1 to 127)
- 128 giá trị cho negative (-128 to -1)
→ Range: -128 to +127 (total 256 values)
Chuyển đổi từ binary:
// Positive: Đọc trực tiếp
byte b1 = 0b0010_1010; // 42
System.out.println(b1); // 42
// Negative: Two's complement
// 1111_1110 = ?
// Step 1: Invert bits → 0000_0001
// Step 2: Add 1 → 0000_0010 = 2
// Step 3: Add minus sign → -2
byte b2 = (byte) 0b1111_1110;
System.out.println(b2); // -2
Tính toán Two's complement:
Để tìm -X (X > 0):
- Lấy binary của X
- Invert tất cả bits (0→1, 1→0)
- Cộng 1
Ví dụ: Tìm -5 (byte):
5 = 0000_0101
Step 1: Invert → 1111_1010
Step 2: Add 1 → 1111_1011 = -5
Verify:
1111_1011
Invert → 0000_0100
Add 1 → 0000_0101 = 5 ✅
Ứng dụng: Overflow behavior
byte max = 127; // 0111_1111
max++; // 1000_0000 = -128 (overflow!)
System.out.println(max); // -128
byte min = -128; // 1000_0000
min--; // 0111_1111 = 127 (underflow!)
System.out.println(min); // 127
Integer overflow không throw exception — nó wrap around (mod 2^n)!
int max = Integer.MAX_VALUE; // 2147483647
int overflow = max + 1; // -2147483648 (wraps to MIN_VALUE!)
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(overflow); // -2147483648
System.out.println(overflow == Integer.MIN_VALUE); // true
Giải pháp: Dùng Math.addExact() (throws ArithmeticException khi overflow):
int safe = Math.addExact(Integer.MAX_VALUE, 1); // ArithmeticException
Ví dụ:
byte age = 25; // Small numbers (-128 to 127)
short year = 2024; // Moderate numbers
int population = 8000000; // Default cho integer literals
long distanceToMoon = 384400000L; // L suffix cho long literals
- byte: Very small numbers (age, status codes)
- short: Moderate numbers (year)
- int: DEFAULT CHOICE cho integers (hầu hết trường hợp)
- long: Very large numbers (timestamps, distances)
Ví dụ thực tế:
// File size
long fileSize = 2147483648L; // 2GB (> int max value)
// Color RGB values (0-255)
// byte range là -128 to 127, nên dùng int cho RGB!
int red = 255;
int green = 128;
int blue = 64;
// Timestamp
long timestamp = System.currentTimeMillis(); // Milliseconds since 1970
2. Floating-Point Types (Số thực)
| Type | Size | Precision | Range | Default Value | Example |
|---|---|---|---|---|---|
float | 4 bytes | ~6-7 decimal digits | ±3.4e38 | 0.0f | float pi = 3.14f; |
double | 8 bytes | ~15 decimal digits | ±1.7e308 | 0.0d | double price = 99.99; |
Ví dụ:
float temperature = 36.5f; // f suffix required
double accountBalance = 1234.56; // Default cho floating literals
double scientific = 1.23e5; // 123000.0 (scientific notation)
- float: PHẢI có suffix
fhoặcFfloat x = 3.14f; // ✅ OK
float y = 3.14; // ❌ Error! (3.14 là double) - double: Suffix
doptionaldouble x = 3.14; // ✅ OK (default)
double y = 3.14d; // ✅ OK (explicit)
Precision comparison:
float f = 0.1f + 0.2f;
double d = 0.1 + 0.2;
System.out.println(f); // 0.30000001 (imprecise)
System.out.println(d); // 0.30000000000000004 (more precise but still imprecise)
IEEE 754 — Tại sao 0.1 + 0.2 ≠ 0.3?
Java sử dụng IEEE 754 standard cho float và double — định dạng binary floating-point representation.
Vấn đề: Nhiều số thập phân không biểu diễn chính xác trong binary.
- Hệ thập phân:
1/3 = 0.333333...(vô hạn) - Hệ nhị phân:
0.1 = 0.00011001100110011...(vô hạn) - Computer chỉ lưu hữu hạn bits → làm tròn → sai số!
Ví dụ cụ thể:
double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c); // 0.30000000000000004
System.out.println(c == 0.3); // false !!!
System.out.println(0.1 + 0.2 == 0.3); // false !!!
// In binary representation:
// 0.1 = 0.0001100110011001100110011001100110011... (recurring)
// 0.2 = 0.001100110011001100110011001100110011... (recurring)
// → Tổng có sai số tích lũy
Cấu trúc IEEE 754 (double — 64 bits):
Formula: Value = (-1)^sign × 1.mantissa × 2^(exponent - 1023)
Giải pháp:
- Dùng BigDecimal cho financial calculations:
// ❌ BAD - Floating-point arithmetic imprecise
double price = 0.1 + 0.2; // 0.30000000000000004
// ✅ GOOD - Exact decimal arithmetic
import java.math.BigDecimal;
BigDecimal price = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(price); // 0.3 (chính xác!)
- So sánh với epsilon (tolerance):
double a = 0.1 + 0.2;
double b = 0.3;
// ❌ Never compare floats with ==
if (a == b) { } // false
// ✅ Compare with tolerance
double EPSILON = 0.0001;
if (Math.abs(a - b) < EPSILON) {
System.out.println("Equal enough!"); // ✅
}
- Dùng int/long cho tiền (cents):
// Thay vì: double dollars = 12.34
// Dùng: int cents = 1234 (store in cents)
int priceInCents = 1234; // $12.34
System.out.printf("$%.2f%n", priceInCents / 100.0); // $12.34
Special values:
double positive_infinity = 1.0 / 0.0; // Infinity
double negative_infinity = -1.0 / 0.0; // -Infinity
double not_a_number = 0.0 / 0.0; // NaN
System.out.println(positive_infinity); // Infinity
System.out.println(negative_infinity); // -Infinity
System.out.println(not_a_number); // NaN
// NaN so sánh:
System.out.println(not_a_number == not_a_number); // false !!!
System.out.println(Double.isNaN(not_a_number)); // true ✅
Underflow/Overflow:
double tooSmall = 1e-400; // 0.0 (underflow)
double tooBig = 1e400; // Infinity (overflow)
Lý do:
- IEEE 754 binary floating-point không phù hợp cho decimal arithmetic
- Sai số tích lũy qua nhiều phép tính
- Financial calculations yêu cầu exact precision
Giải pháp:
- Dùng
BigDecimal(chậm hơn nhưng chính xác) - Hoặc lưu bằng
int/long(cents, mili-dollars)
3. Character Type
| Type | Size | Range | Default Value | Example |
|---|---|---|---|---|
char | 2 bytes | 0 to 65,535 (Unicode) | '\u0000' | char grade = 'A'; |
Ví dụ:
char letter = 'A'; // Single quotes
char digit = '9'; // Character, not integer 9
char unicode = '\u0041'; // 'A' in Unicode
char newline = '\n'; // Escape sequence
Escape sequences:
| Sequence | Meaning | Example |
|---|---|---|
\n | Newline | "Line 1\nLine 2" |
\t | Tab | "Col1\tCol2" |
\\ | Backslash | "C:\\Users" |
\' | Single quote | '\'' |
\" | Double quote | "He said \"Hi\"" |
\r | Carriage return | "Text\r" |
System.out.println("Line 1\nLine 2"); // Two lines
System.out.println("Name:\tAlice"); // Tab-separated
System.out.println("Path: C:\\Users"); // Backslash
System.out.println("He said \"Hello\""); // Quotes
4. Boolean Type
| Type | Size | Values | Default Value | Example |
|---|---|---|---|---|
boolean | 1 bit (implementation-dependent) | true, false | false | boolean isActive = true; |
Ví dụ:
boolean isStudent = true;
boolean hasLicense = false;
boolean canVote = age >= 18; // Expression result
Common use cases:
// Flags
boolean isActive = true;
boolean isDeleted = false;
// Conditions
boolean isAdult = age >= 18;
boolean isEligible = isAdult && hasLicense;
// Control flow
if (isActive) {
System.out.println("Active!");
}
Boolean variables nên bắt đầu bằng is, has, can:
boolean isValid; // ✅
boolean hasPermission; // ✅
boolean canDelete; // ✅
boolean active; // ❌ Không rõ ràng
Primitive Types Summary
// Integer types
byte b = 127; // 1 byte
short s = 32000; // 2 bytes
int i = 2000000; // 4 bytes (DEFAULT)
long l = 9000000000L; // 8 bytes (suffix L)
// Floating-point types
float f = 3.14f; // 4 bytes (suffix f)
double d = 3.14159265359; // 8 bytes (DEFAULT)
// Character type
char c = 'A'; // 2 bytes (Unicode)
// Boolean type
boolean bool = true; // 1 bit (true/false)
Default values (for class fields):
public class Defaults {
byte b; // 0
short s; // 0
int i; // 0
long l; // 0L
float f; // 0.0f
double d; // 0.0d
char c; // '\u0000' (null character)
boolean bool; // false
}
Reference Types
Reference types lưu địa chỉ (reference) của object trong memory, không lưu giá trị trực tiếp.
Reference vs Primitive
// Primitive: Lưu giá trị trực tiếp
int x = 10;
int y = x; // Copy giá trị
y = 20;
System.out.println(x); // 10 (không thay đổi)
// Reference: Lưu địa chỉ
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // Copy địa chỉ (cùng trỏ đến object)
arr2[0] = 99;
System.out.println(arr1[0]); // 99 (thay đổi!)
Memory diagram:
Common Reference Types
1. String
String name = "Alice";
String greeting = new String("Hello");
2. Arrays
int[] numbers = {1, 2, 3, 4, 5};
String[] names = {"Alice", "Bob", "Charlie"};
3. Classes
Scanner scanner = new Scanner(System.in);
ArrayList<String> list = new ArrayList<>();
4. Interfaces
List<String> list = new ArrayList<>(); // List is interface
null - Special value
Reference types có thể có giá trị null (không trỏ đến object nào).
String name = null; // ✅ OK - reference type
int age = null; // ❌ Error - primitive type không có null
NullPointerException:
String name = null;
System.out.println(name.length()); // ❌ NullPointerException!
// Safe check
if (name != null) {
System.out.println(name.length()); // ✅ OK
}
Khai báo và Khởi tạo Biến
Syntax
// Declaration (khai báo)
int age;
// Initialization (khởi tạo)
age = 25;
// Declaration + Initialization (một dòng)
int height = 175;
// Multiple variables (cùng type)
int x, y, z;
x = 1;
y = 2;
z = 3;
// Multiple variables với giá trị
int a = 1, b = 2, c = 3;
Local variables PHẢI được khởi tạo trước khi sử dụng!
public void method() {
int x;
System.out.println(x); // ❌ Error: variable might not have been initialized
int y = 10;
System.out.println(y); // ✅ OK
}
Variable Naming Rules
Rules (bắt buộc):
- Bắt đầu bằng letter (a-z, A-Z),
$, hoặc_ - Có thể chứa letters, digits,
$,_ - KHÔNG thể là keyword (
int,class,public, ...) - Phân biệt HOA/thường:
age≠Age
// ✅ Valid
int age;
int _count;
int $price;
int studentName;
int student2;
// ❌ Invalid
int 2student; // Bắt đầu bằng số
int my-name; // Có dấu gạch ngang
int class; // Keyword
Best practices:
// ✅ GOOD - Descriptive, camelCase
int studentAge;
double accountBalance;
boolean isActive;
// ❌ BAD - Not descriptive
int a;
int x123;
int temp;
final Keyword - Constants
final để khai báo constants (giá trị không thay đổi).
final int MAX_SIZE = 100;
MAX_SIZE = 200; // ❌ Error: cannot assign a value to final variable
Convention:
// Constants: UPPER_SNAKE_CASE
final int MAX_USERS = 1000;
final double PI = 3.14159;
final String APP_NAME = "My Application";
Class-level constants:
public class Config {
public static final int MAX_CONNECTIONS = 100;
public static final String DATABASE_URL = "jdbc:mysql://localhost:3306/mydb";
}
// Usage
System.out.println(Config.MAX_CONNECTIONS);
Benefits:
- ✅ Prevent accidental changes
- ✅ More readable than magic numbers
- ✅ Easier to maintain (change once, effect everywhere)
// ❌ BAD - Magic number
if (age >= 18) {
// ...
}
// ✅ GOOD - Named constant
final int VOTING_AGE = 18;
if (age >= VOTING_AGE) {
// ...
}
Variable Scope
Scope là phạm vi mà variable có thể được truy cập.
1. Local Variables
Khai báo trong method hoặc block { }.
public void method() {
int x = 10; // Local variable
System.out.println(x); // ✅ OK
}
// System.out.println(x); // ❌ Error: x not visible here
Block scope:
public void method() {
int x = 10;
if (true) {
int y = 20;
System.out.println(x); // ✅ OK (x visible)
System.out.println(y); // ✅ OK (y visible)
}
System.out.println(x); // ✅ OK
// System.out.println(y); // ❌ Error: y not visible outside block
}
2. Instance Variables (Fields)
Khai báo trong class, bên ngoài methods. Mỗi object có copy riêng.
public class Student {
// Instance variables
private String name;
private int age;
public void setName(String name) {
this.name = name; // Access instance variable
}
}
3. Class Variables (Static)
Khai báo với static keyword. Shared giữa tất cả objects.
public class Counter {
private static int count = 0; // Class variable
public Counter() {
count++; // Tăng cho tất cả instances
}
public static int getCount() {
return count;
}
}
// Usage
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(Counter.getCount()); // 2
Scope Summary
| Scope | Khai báo | Lifetime | Access |
|---|---|---|---|
| Local | Trong method/block | Khi method/block execute | Chỉ trong method/block |
| Instance | Trong class (non-static) | Khi object tồn tại | Qua object reference |
| Class (static) | Trong class (static) | Khi class load | Qua class name |
Literals
Literal là giá trị cố định trong code.
Integer Literals
int decimal = 100; // Decimal (base 10)
int binary = 0b1100100; // Binary (base 2) - prefix 0b
int octal = 0144; // Octal (base 8) - prefix 0
int hex = 0x64; // Hexadecimal (base 16) - prefix 0x
// Tất cả = 100 (decimal)
Underscore for readability (Java 7+):
int million = 1_000_000; // 1 million
long creditCard = 1234_5678_9012_3456L;
int binary = 0b1010_1100_0011;
Floating-Point Literals
double d1 = 123.456;
double d2 = 1.23e2; // 123.0 (scientific notation)
double d3 = 1.23E-2; // 0.0123
float f = 3.14f; // f suffix
Character Literals
char c1 = 'A';
char c2 = 65; // ASCII value of 'A'
char c3 = '\u0041'; // Unicode for 'A'
char newline = '\n'; // Escape sequence
String Literals
String s1 = "Hello";
String s2 = "Hello\nWorld";
String s3 = "C:\\Users\\Alice";
String s4 = ""; // Empty string
Text Blocks (Java 13+ preview, Java 15+ final):
// Text block - multi-line string literal
String json = """
{
"name": "Alice",
"age": 25
}
""";
// Không cần escape quotes hay dùng \n
String html = """
<html>
<body>Hello</body>
</html>
""";
Boolean Literals
boolean t = true;
boolean f = false;
Bài tập thực hành
Bài 1: Khai báo và sử dụng biến
Viết chương trình calculator đơn giản:
- Nhập 2 số (double)
- In ra: tổng, hiệu, tích, thương
- Format output với 2 chữ số thập phân
Expected output:
Nhập số thứ nhất: 10.5
Nhập số thứ hai: 3.2
Tổng: 13.70
Hiệu: 7.30
Tích: 33.60
Thương: 3.28
Xem đáp án
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Nhập số thứ nhất: ");
double num1 = sc.nextDouble();
System.out.print("Nhập số thứ hai: ");
double num2 = sc.nextDouble();
double sum = num1 + num2;
double diff = num1 - num2;
double product = num1 * num2;
double quotient = num1 / num2;
System.out.printf("Tổng: %.2f%n", sum);
System.out.printf("Hiệu: %.2f%n", diff);
System.out.printf("Tích: %.2f%n", product);
System.out.printf("Thương: %.2f%n", quotient);
sc.close();
}
}
Bài 2: Student Info
Viết chương trình khai báo thông tin sinh viên sử dụng các kiểu dữ liệu khác nhau:
- Khai báo: name (String), age (byte), gpa (double), isActive (boolean), grade (char)
- In ra tất cả thông tin
Xem đáp án
public class StudentInfo {
public static void main(String[] args) {
String name = "Nguyen Van A";
byte age = 20;
double gpa = 3.75;
boolean isActive = true;
char grade = 'A';
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.printf("GPA: %.2f%n", gpa);
System.out.println("Active: " + isActive);
System.out.println("Grade: " + grade);
}
}
Tổng kết
Trong bài này, bạn đã học:
✅ Primitive types: byte, short, int, long, float, double, char, boolean ✅ Reference types: String, arrays, objects (lưu địa chỉ, có thể null) ✅ final keyword: Constants (UPPER_SNAKE_CASE) ✅ Variable scope: Local, instance, class (static) ✅ Literals: Integer, floating-point, character, string, boolean, text blocks (Java 15+)
Key differences:
| Aspect | Primitive | Reference |
|---|---|---|
| Lưu trữ | Giá trị trực tiếp | Địa chỉ (reference) |
| null | ❌ Không có | ✅ Có |
| Default | 0, false, '\u0000' | null |
| Comparison | == (giá trị) | == (địa chỉ), .equals() (giá trị) |
| Memory | Stack | Heap (object), Stack (reference) |
Bài tiếp theo chúng ta sẽ học về Type Casting và var Keyword - chuyển đổi kiểu dữ liệu và type inference!