Kiểu dữ liệu (1)

Trong bài này, chúng ta discuss về kiểu dữ liệu.
Ngôn ngữ Java là ngôn ngữ tĩnh (statically typed language). Có nghĩa là mỗi biến, mỗi biểu thức có kiểu dữ liệu được biết tại thời điểm compile time. Ngôn ngữ Java cũng là ngôn ngữ strong type vì các kiểu dữ liệu giới hạn giá trị của biến có thể lưu trữ hoặc giá trị của các biểu thức, giới hạn các phép toán được support cho các biến và quyết định ý nghĩa của phép toán. Strong static typing giúp defect error tại thời điểm compile. Các biến dynamic typed languages như Ruby hoặc Python có thể nhận các kiểu dữ liệu khác nhau trong suốt chương trình. Trong Java, 1 biến được khai báo phải đi kèm với kiểu dữ liệu, nó không thể lưu trữ giá trị của kiểu dữ liệu khác.
Có 2 kiểu dữ liệu trong Java: kiểu dữ liệu nguyên thủy (primitive types) và kiểu tham chiếu (reference types).
Kiểu dữ liệu nguyên thủy gồm có:

boolean
char
byte
short
int
long
float
double

Mỗi từ khóa tương ứng với mỗi kiểu dữ liệu trong Java. Kiểu dữ liệu nguyên thủy không phải là đối tượng trong Java.
Kiểu dữ liệu tham chiếu gồm có:

boolean
class
interface
array
double

Ngoài ra, có kiểu null là kiểu dữ liệu đặc biệt, mô tả giá trị không tồn tại.
Trong ngôn ngữ Ruby, mọi thứ đều là object, thậm chị là kiểu dữ liệu cơ bản.

#!/usr/bin/ruby
4.times { puts "Ruby" }

Đây là Ruby script in ra 4 lần chuỗi “Ruby” trên màn hình console. Chúng ta gọi phương thức times 4 lần. Số 4 là 1 object trong Ruby.
Java có 1 phương pháp khác. Nó có kiểu dữ liệu nguyên thủy và wrapper class. Wrapper class chuyển đổi dữ liệu nguyên thủy thành đối tượng. Wrapper class sẽ được discuss ở bài viết tiếp theo.

Boolean

Luôn có 2 mặt đối lập trong thế giới tự nhiên. Ví dụ như: thiên đường và trái đất, nước và lửa, đàn ông và phụ nữ, tình yêu và thù hận,… Trong Java, kiểu dữ liệu boolean là kiểu dữ liệu nguyên thủy có 2 giá trị: true hoặc false.
Một gia đình hạnh phúc đang đợi đứa con chào đời. Họ đã lựa chọn tên cho cả 2 khả năng. Nếu là con trai, họ đặt là Robert. Nếu là bé gái, họ đặt là Victoria.

package net.vncoding;

import java.util.Random;

public class BooleanType {

    public static void main(String[] args) {
        
        String name = "";
        Random r = new Random();
        boolean male = r.nextBoolean();

        if (male == true) {
            name = "Robert";
        }

        if (male == false) {
            name = "Victoria";
        }

        System.out.format("We will use name %s%n", name);

        System.out.println(9 > 8);
    }
}

Ví dụ này sử dụng phương thức tạo ra số ngâu nhiên để mô phỏng trường hợp sinh con trai hoặc con gái.

Random r = new Random();
boolean male = r.nextBoolean();

Class Random được sử dụng để tạo số ngẫu nhiên. Phương thức nextBoolean() trả về số ngẫu nhiên giá trị boolean.

if (male == true) {
    name = "Robert";
}

Nếu giá trị male = true, chúng ta gán biến name = “Robert”. Biểu thức điều kiện câu lệnh if() có giá trị boolean.

if (male == false) {
    name = "Victoria";
}

Nếu giá trị nale = false, chúng ta gán biến name = “Victoria”.

System.out.println(9 > 8);

Toán tử quan hệ trả về kết quả boolean. (9 > 8) = true.

Kết quả:

Số Nguyên

Số nguyên là số thuộc tập Z = {…, -2, -1, 0, 1, 2, …}.
Trong ngôn ngữ máy tính, số nguyên là kiểu dữ liệu nguyên thủy. Máy tính có thể làm việc thực tế chỉ với 1 phần nhỏ bộ số nguyên, bởi vì khả năng xử lí máy tính là có hạn. Số nguyên được sử dụng để đếm các trường rời rạc. Chúng ta có thể có 3,4 hoặc 6 người, nhưng chúng ta không thể có 3.33 người. Chúng ta có 3.33 kg, 4.56 ngày hoặc 0.457 km.

Bảng kiểu dữ liệu nguyên trong Java

Kiểu dữ liệu Kích thước Phạm vi
byte 8 bits -128 ~ 127
short 16 bits -32768 ~ 32767
char 16 bits 0 ~ 65535
int 32 bits -2,147,483,648 to 2,147,483,647
long 64 bits -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

Các kiểu dữ liệu nguyên được sử dụng theo nhu cầu. Chúng ta có thể sử dụng kiểu byte cho biến lưu số lượng người con mà 1 phụ nữ sinh ra. Người sống thọ nhất trên thế giới là 122 tuổi, do đó chúng ta có thể chọn kiểu short để lưu trữ tuổi đời của con người. Điều này sẽ giúp tích kiệm được memory.
Số nguyên có thể được biểu diễn dưới dạng hệ cơ số 10, Hexa (cơ số 16), Octal (cơ số 8) hoặc binary (cơ số 2). Nếu số có kí tự ASCII hoặc hậu tố l, nó là kiểu long. Ngược lại, nó là kiểu int. Chữ in hoa L thường được chọn để chỉ định số long, vì chữ in thường l dễ gây nhầm lẫn với số 1.

int a = 34;
byte b = 321;
short c = 3200;
long d = 45000;
long e = 23333333L;

Chúng ta có 5 phép gán. Giá trị 34, 321, 3200 và 45000 là hằng số kiểu int. Đối với hằng số kiểu int và short không cần thêm hậu tố. Nếu giá trị hằng số khớp với kiểu dữ liệu, compiler không cần convert kiểu dữ liệu. Đối với kiểu long nhỏ hơn giá trị Integer.MAX_VALUE, hậu tố L là không bắt buộc.

long x = 2147483648L;
long y = 2147483649L;

Đối với kiểu long lớn hơn Integer.MAX_VALUE, chúng ta phải thêm hậu tố L.
When chúng ta làm việc với số nguyên, chúng ta giải quyết các bài toán rời rạc. Ví dụ: chúng ta có thể sử dụng số nguyên để đếm số lượng quả táo.

package net.vncoding;

public class Apples {

    public static void main(String[] args) {

        int baskets = 16;
        int applesInBasket = 24;

        int total = baskets * applesInBasket;

        System.out.format("There are total of %d apples%n", total);
    }
}

Trong ví dụ này, chúng ta đếm số lượng táo. Chúng ta sử dụng toán tử nhân.

int baskets = 16;
int applesInBasket = 24;

Số lượng sọt và số lượng táo trong mỗi sọt là số nguyên

int total = baskets * applesInBasket;

Phép nhân 2 số nguyên cho ra kết quả là số nguyên.

Kết quả:

Số nguyên có thể được biểu diễn bởi 4 kiểu khác nhau: cơ số 10, octal ( cơ số 8), hexa (cơ số 16) và nhị phân (cơ số 2). Kiểu biểu diễn nhị phân được giới thiệu trong Java 7. Cơ số 10 được sử dụng bình thường như các bạn đã biết. Cơ số 8 được bắt đầu bởi kí tự 0 và theo sau bởi số octal. Cơ số 16 được bắt đầu bởi kí tự 0x và theo sau bởi số hexa. Cơ số 2 bắt đầu với 0b và theo sau bởi số nhị phân (0 và 1).

package net.zetcode;

public class IntegerNotations {

    public static void main(String[] args) {
        
        int n1 = 31;
        int n2 = 0x31;
        int n3 = 031;
        int n4 = 0b1001;
        
        System.out.println(n1);
        System.out.println(n2);
        System.out.println(n3);
        System.out.println(n4);        
    }
}

Chúng ta có 4 biến số nguyên. Mỗi biến được gán bởi số nguyên với các cách biểu diễn khác nhau.

int n1 = 31;
int n2 = 0x31;
int n3 = 031;
int n4 = 0b1001;

Số đầu tiên là cơ số 10, số thứ 2 là hexa, số thứ 3 là octal, số thứ 4 là nhị phân.

Kết quả:

Số lớn thường rất khó đọc. Nếu chúng ta có số giống như 245342395423452, chúng ta cảm thấy khó để đọc nhanh được. Kể từ Java SE 1.7, chúng ta có thể phân chia số nguyên bằng dấu gạch chân.
Dấu gạch dưới không được phép sử dụng tại chữ số đầu tiên và kết thúc của số nguyên, bên cạnh dấu thập phân trong số dấu phẩy động, và trước hậy tố F hoặc L.

package net.vncoding;

public class UsingUnderscores {
  
    public static void main(String[] args) {
        
        long a = 23482345629L;
        long b = 23_482_345_629L;

        System.out.println(a == b);
    }    
}

Ví dụ minh họa cách sử dụng dấu gạch dưới trong Java để phân tách các số nguyên lớn –> dễ đọc hơn.

long a = 23482345629L;
long b = 23_482_345_629L;

Chúng ta có 2 cách biểu diễn số nguyên kiểu long. Cách biểu diễn thứ 2 chia 3 chữ số làm 1 số. So sánh 2 cách biểu diễn, chúng ta nhận được giá trị true từ biểu thức if. Hậu tố L thông báo cho compiler là số nguyên kiểu long.
Kiểu byte, short, int và long được sử dụng để mô tả các số có độ chính xác cố định. Nghĩa là chúng có thể biểu diễn các số nguyên hữu hạn. Số nguyên lớn nhất mà kiểu long có thể biểu diễn được là 9223372036854775807.

Nếu chúng ta cần xử lí các số nguyên lớn hơn nữa, chúng ta phải sử dụng class java.math.BigInteger. Nó được sử dụng để biểu diễn số nguyên tùy ý. Số nguyên chính xác tùy ý chỉ giới hạn bởi memory của máy tính.

package net.vncoding;

import java.math.BigInteger;

public class VeryLargeIntegers {

    public static void main(String[] args) {
        
        System.out.println(Long.MAX_VALUE);
        
        BigInteger b = new BigInteger("92233720368547758071");
        BigInteger c = new BigInteger("52498235605326345645");
               
        BigInteger a = b.multiply(c);
        
        System.out.println(a);            
    }
}

Với sự giúp đỡ của class java.math.BigInteger, chúng ta nhân 2 số lớn.

System.out.println(Long.MAX_VALUE);

In ra giá trị nguyên lớn nhất mà kiểu long có thể biểu diễn.

BigInteger b = new BigInteger("92233720368547758071");
BigInteger c = new BigInteger("52498235605326345645");

Chúng ta định nghĩa 2 đối tượng BigInteger. Cả 2 đều giữ giá trị lớn hơn kiểu long có thể biểu diễn.

BigInteger a = b.multiply(c);

Với phương thức multiply(), chúng ta nhân 2 số. Chú ý rằng các số BigInteger là bất biến. Kết quả của phép nhân được gán cho đối tượng BigInteger.

System.out.println(a);

Kết quả của phép nhân 2 số nguyên lớn.

Java - Biểu diễn số lớn
Java – Biểu diễn số lớn

Tràn số

Tràn số là hiện tượng xảy ra khi tính toán tạo ra kết quả lớn, vượt ra ngoài khả năng lưu trữ của các biến.

package net.vncoding;

/*
 This is Overflow program
 Date: 2017/11/18 
 */

public class Overflow {

    public static void main(String[] args) {
        
        byte a = 126;

        System.out.println(a);
        a++;

        System.out.println(a);
        a++;

        System.out.println(a);
        a++;

        System.out.println(a);
    }
} 

Trong ví dụ này, chúng ta gán giá trị vượt qua phạm vi lưu trữ của kiểu byte. Dẫn đến tràn số!!!

Java - Tràn số
Java – Tràn số
Khi tràn số, biến được reset về giá trị min của kiểu dữ liệu.

Số dấu phẩy động

Số thực đo số lượng liên tục như cần nặng, chiều cao, vận tốc. Số dấu phẩy động biểu diễn xấp xỉ (gần chính xác) số thực trong tính toán. Trong Java, chúng ta có 2 kiểu dữ liệu nguyên thủy biểu diễn số dấu phẩy động: float và double. Kiểu float có độ chính xác đơn lưu các chữ số trong 32 bit. Kiểu double có độ chính xác double lưu các chữ số trong 64 bit. Cả 2 kiểu float và double đều có độ chính xác cố định và không thể mô tả chính xác tất cả các số thực. Trong trường hợp chúng ta phải làm việc với số chính xác, chúng ta có thể sử dụng calss BigDecimal.
Số dấu phẩy động với hậu tố F hoặc f là kiểu float, kiểu double với hậu tố D hoặc d. Hậu tố cho kiểu double là tùy ý.

Ví dụ: Vận động viên chạy 100m mất 9.87s. Vậy anh ấy chạy bao nhiêu km/h?

package net.vncoding;

public class Sprinter {

    public static void main(String[] args) {
        
        float distance;
        float time;
        float speed;

        distance = 0.1f;
        
        time = 9.87f / 3600;

        speed = distance / time;

        System.out.format("The average speed of a sprinter is %f km/h%n", speed);
    }
}

Trong ví dụ này, nên sử dụng kiểu dấu phẩy động. Mặc dù độ chính xác của kiểu float thấp, nhưng có thể chấp nhận trong bài toán này.

distance = 0.1f;

100m là 0.1km

time = 9.87f / 3600;

9.87s là 9.87/60*60h

speed = distance / time;

Chúng ta tính vận tốc bằng công thức quãng đường chia thời gian.

Java - Kiểu dữ liệu float
Java – Kiểu dữ liệu float

Tiếp theo, chúng ta xem ví dụ minh họa kiểu float và double là không chính xác.

package net.vncoding;

public class FloatingInPrecision {

    public static void main(String[] args) {        
                
        double a = 0.1 + 0.1 + 0.1;
        double b = 0.3;
        
        System.out.println(a);
        System.out.println(b);
        
        System.out.println(a == b);        
    }
}
double a = 0.1 + 0.1 + 0.1;
double b = 0.3;

Chúng ta định nghĩa 2 giá trị double. Hậu tố D hoặc d là không bắt buộc. Thoáng nhìn, tôi nghĩ a = b.

System.out.println(a);
System.out.println(b);

Kết quả in ra có khác nhau 1 chút.

System.out.println(a == b);  

Dòng code trả về giá trị false.

Java - Kiểu dữ liệu double
Java – Kiểu dữ liệu double

Khi chúng ta làm việc lĩnh vực tiền tệ, ngân hàng và nói chung là các ứng dụng trong kinh doanh, chúng ta cần làm việc với số chính xác. Việc biểu diễn xấp xỉ không được chấp nhận.

package net.vncoding;

public class CountingMoney {

    public static void main(String[] args) {
        
        float c = 1.46f;
        float sum = 0f;
        
        for (int i=0; i<100_000; i++) {
            
            sum += c;
        }
        
        System.out.println(sum);     
    }
}

Số 1.46f biểu diễn 1 euro và 46 cent. Giả sử chúng ta có 100000 tờ tiền 1 euro và 46 cent.

for (int i=0; i<100_000; i++) {   
    sum += c;
}

Chúng ta dùng vòng lặp 100000 lần để tính tổng.
Kết quả:

Java - Sai số khi sử dụng kiểu dữ liệu float
Java – Sai số khi sử dụng kiểu dữ liệu float

Kết quả thừa 2 euro và 55 cent.
Để tránh lỗi này, chúng ta sử dụng class BigDecimal. Nó có thể biểu diễn số dấu phẩy động với độ chính xác tùy ý.

package net.vncoding;

import java.math.BigDecimal;

public class CountingMoney2 {

    public static void main(String[] args) {
    
        BigDecimal c = new BigDecimal("1.46");
        BigDecimal sum = new BigDecimal("0");
        
        for (int i=0; i<100_000; i++) {
            
            sum = sum.add(c);
        }
        
        System.out.println(sum);                
    }
}

Giống ví dụ ở trên, trong ví dụ này chúng ta tạo tổng bằng vòng lặp 100000 lần.

BigDecimal c = new BigDecimal("1.46");
BigDecimal sum = new BigDecimal("0");

Chúng ta định nghĩa 2 số BigDecimal.

for (int i=0; i<100_000; i++) {
    sum = sum.add(c);
}

Số BigDecinal là bất biến, do đó đối tượng mới luôn được gán cho biến sum mỗi chu kì của vòng lặp.
Kết quả:

Java - Biểu diễn số phẩy động chính xác
Java – Biểu diễn số phẩy động chính xác
Bằng cách sử dụng BigDecimal, chúng ta có thể lấy được giá trị chính xác.

Java support biểu diễn số dấu phẩy động dưới dạng số mũ. Cách biểu diễn này thuận tiện cho việc biểu diễn các số rất lớn hoặc rất nhỏ.

package net.vncoding;

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class ScientificNotation {

    public static void main(String[] args) {        
        
        double n = 1.235E10;                        
        DecimalFormat dec = new DecimalFormat("#.00");
        
        System.out.println(dec.format(n));        
        
        BigDecimal bd = new BigDecimal("1.212e-19");
        
        System.out.println(bd.toEngineeringString());
        System.out.println(bd.toPlainString());                          
    }
}

Chúng ta định nghĩa 2 số dấu phẩy động, với giá trị khởi tạo kiểu mũ.

double n = 1.235E10;  

Đây là giá trị dấu phẩy động kiểu double, được viết dưới dạng mũ.

DecimalFormat dec = new DecimalFormat("#.00");
System.out.println(dec.format(n));   

Chúng ta sử dụng class DecimalFormat để sắp xếp giá trị double thành định dạng cơ số 10.

BigDecimal bd = new BigDecimal("1.212e-19");
System.out.println(bd.toEngineeringString());
System.out.println(bd.toPlainString()); 

Class BigDecimal lấy giá trị dấu phẩy động “1.212e-19” làm tham số đầu vào. Chúng ta sử dụng 2 phương thức của class để in giá trị thành chuỗi engineering và plain.

Java - Biểu diễn dấu phẩy động dưới dạng mũ
Java – Biểu diễn dấu phẩy động dưới dạng mũ

Enumerations

Kiểu enum là kiểu dữ liệu đặc biệt, cho phép 1 biến có thể là tập hợp của các hằng số định sẵn. Một biến đã được khai báo phải có 1 bộ các hằng số đi kèm. Kiểu enum làm cho code dễ đọc hơn. Kiểu enum hữu ích khi chúng ta xử lý các biến chỉ có thể sử dụng 1 trong các giá trị.

package net.vncoding;

public class Enumerations {

    enum Days {

        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY
    }

    public static void main(String[] args) {

        Days day = Days.MONDAY;

        if (day == Days.MONDAY) {  
            System.out.println("It is Monday");
        }

        System.out.println(day);        

        for (Days d : Days.values()) {
            System.out.println(d);
        }
    }
}

Trong đoạn code này, chúng ta tạo kiểu enum cho các ngày trong tuần.

enum Days {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

Kiểu enum được tạo với từ khóa enum, biểu diễn các ngày trong tuần. Các phần tử của enum là các hằng số. Theo coding convention, hằng số được viết bằng chữ in hoa.

Days day = Days.MONDAY;

Chúng ta có biến day và được khởi tạo là MONDAY.

if (day == Days.MONDAY) {
    System.out.println("It is Monday");
}

Việc định nghĩa kiểu enum làm cho đoạn code dễ đọc và dễ hiểu hơn.

System.out.println(day);

Lệnh này in Monday ra màn hình console.

for (Days d : Days.values()) {
    System.out.println(d);
}

Vòng lặp in tất cả các ngày ra màn hình console. Phương thức values() trả về mảng chứa các hằng số của kiểu enum theo thứ tự mà chúng được khai báo. Phương thức này có thể được sử dụng để duyệt qua các hằng số của kiểu enum. Điều này gần giống với vòng lặp for.

Java - Kiểu enum
Java – Kiểu enum

Ngoài ra, chúng ta có thể gán giá trị cho hằng số của kiểu enum.

package net.vncoding;

enum Season {

    SPRING(10),
    SUMMER(20),
    AUTUMN(30),
    WINTER(40);

    private int value;

    private Season(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

public class Enumerations2 {

    public static void main(String[] args) {

        for (Season season : Season.values()) {
            System.out.println(season + " " + season.getValue());
        }
    }
}

Trong ví dụ này, biến enum Season có 4 hằng số.

SPRING(10),
SUMMER(20),
AUTUMN(30),
WINTER(40);

Chúng ta có thể khởi tạo cho các hằng số.

private int value;
private Season(int value) {
    this.value = value;
}

Khi chúng ta định nghĩa các hằng số, chúng ta phải tạo hàm tạo. (Hàm tạo sẽ được nói trong các bài viết tiếp theo).

Kết quả:

Java - Khởi tạo giá trị cho hằng số enum
Java – Khởi tạo giá trị cho hằng số enum

Chuỗi kí tự và kí tự

Chuỗ kí tự là kiểu dữ liệu biểu diễn dữ liệu text trong chương trình máy tính. Chuỗi kí tự trong Java là 1 chuỗi các kí tự kiểu char. Hằng chuỗi kí tự được được đánh dấu bởi dấu nháy kép.
Vì chuỗi kí tự rất quan trọng trong mỗi ngôn ngữ lập trình, chúng ta sẽ giành riêng 1 chương để nói về chuỗi kí tự. Dưới đây chỉ là 1 ví dụ nhỏ minh họa về chuỗi kí tự.

package net.vncoding;

public class StringsChars {

    public static void main(String[] args) {

        String word = "VnCoding";

        char c = word.charAt(0);
        char d = word.charAt(3);

        System.out.println(c);
        System.out.println(d);
    }
}

Chương trình in ra kí tự V và o.

String word = "VnCoding";

Tạo 1 biến chuỗi kí tự và khởi tạo giá trị là “VnCoding”.

char c = word.charAt(0);

Phương thức charAt() trả về giá trị kiểu char tại chỉ số được chỉ định. Kí tự đầu tiên tại chỉ số 0, kí tự tiếp theo tại chỉ số 1,…

Mảng

Mảng là 1 tập hợp gồm các phần tử có cùng kiểu dữ liệu. Mỗi phần tử có thể được truy cập trực tiếp bởi chỉ số mảng.
Chúng ta sẽ giành 1 chương riêng để nói về mảng. Dưới đây là ví dụ nhỏ minh họa.

package net.vncoding;

public class ArraysEx {

    public static void main(String[] args) {

        int[] numbers = new int[5];

        numbers[0] = 3;
        numbers[1] = 2;
        numbers[2] = 1;
        numbers[3] = 5;
        numbers[4] = 6;

        int len = numbers.length;

        for (int i = 0; i < len; i++) {
            System.out.println(numbers[i]);
        }
    }
}

Trong ví dụ này, chúng ta khai báo 1 mảng, gán giá trị cho mỗi phần tử của mảng, sau đó in nội dung của mảng ra màn hình console.

int[] numbers = new int[5];

Chúng ta tạo mảng số nguyên có thể chứa được tối đa 5 phần tử. Như vậy, chúng ta có mảng 5 phần tử với chỉ số từ 0,1,2,3,4.

numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;

Chúng ta gán giá trị cho mảng. Chúng ta có thể truy cập tới phần tử của mảng bởi chỉ số của mảng nằm trong dấu ngoặc vuông [].

int len = numbers.length;

Mỗi mảng có đặc tính length, trả về số phần tử của mảng.

for (int i = 0; i < len; i++) {
    System.out.println(numbers[i]);
}

Vòng lặp duyệt và in các phần tử của mảng ra màn hình console.

Java - Mảng
Java – Mảng

Be the first to comment

Leave a Reply