Sử dụng Generic trong ngôn ngữ lập trình Java

Giới thiệu nội dung bài viết

Generic – tham số hoá kiểu dữ liệu, một thuật ngữ được nhắc đến thường xuyên trong quá trình các bạn học ngôn ngữ lập trình Java. Một trong những lợi ích Generic mang lại cho những lập trình viên Java, đó là cho phép người dùng có thể dễ dàng nhận ra các lỗi với các kiểu dữ liệu không hợp lệ.

Để giúp các bạn hiểu rõ hơn về kiến thức lập trình Java nâng cao này, bài chia sẻ dưới đây anh sẽ giải thích các khái niệm, thuật ngữ, các kí tự dùng trong Generic. Đồng thời hướng dẫn cách để tạo ra Generic Class và Generic Method. Cũng như chỉ ra các ưu, nhược điểm của Generic để giúp các bạn hiểu được khi nào thì nên sử dụng trong lập trình Java.

1. Generic trong lập trình Java là gì

Generic có nghĩa là ta viết các phương thức và lớp để tái sử dụng cho các đối tượng thuộc các kiểu dữ liệu khác nhau (Kiểu dữ liệu như Person , Car , Student, Hotel vv).Nghe có vẻ khó hiểu nên anh sẽ trình bày ví dụ sau đây.

Ví dụ anh muốn viết một chương trình quản lý danh sách học sinh và giáo viên tại trường đào tạo
công nghệ thông tin Ada. Anh sẽ sử dụng List để a lưu lại danh sách của học sinh và giáo viên như sau. Nếu các bạn đã quên về List là gì thì có thể tham khảo lại các loại tập hợp trong lập trình java tại (đây)[https://levunguyen.com/laptrinhjava/2020/04/04/cac-tap-hop-trong-lap-trinh-java/]

  • Danh sách học sinh

List

<

Student

>

students

=

new

ArrayList

<

Student

>();

  • Danh sách giáo viên

List

<

Teacher

>

teachers

=

new

ArrayList

<

Teacher

>();

Oh, có một điều đặc biệt tại sao List lúc thì chứa đối tượng sinh viên , lúc thì chứa đối tượng là giáo viên. Điều này có kỳ lạ không ?
Bởi vì List được cài đặt theo cách generic nên ta có thể tái sử dụng lại được các kiểu dữ liệu khác nhau (lúc thì chứa sinh viên , lúc thì chứa giáo viên). Sinh viên và giáo viên
là 2 kiểu dữ liệu khác nhau. Do vậy tuỳ vào ngữ cảnh ta truyền vào cho List thì nó có là danh sách sinh viên (List <Student >) hay nó có thể là danh sách giáo viên (List < Teacher >).
Nói cách khác Generic thì ta định nghĩa một kiểu dữ liệu chung chung , và tuỳ vào ngữ cảnh ta truyền vào (Student hay Teacher) thì ta sẽ có tập hợp tương ứng.

2. Cách tạo Generic Class và Generic method

1. Cách tạo Generic Class

Ví dụ ta tạo Generic Class tên là Box. Mọi người chú ý để tạo 1 class là generic ta thêm < T > vào sau class. <T> là ký hiệu của Generic , ta sẽ tìm hiểu ở phần tiếp theo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   

public

class

Box

<

T

>

{

private

T

t

;

public

void

add

(

T

t

)

{

this

.

t

=

t

;

}

public

T

get

()

{

return

t

;

}

public

static

void

main

(

String

[]

args

)

{

Box

<

Integer

>

integerBox

=

new

Box

<

Integer

>();

Box

<

String

>

stringBox

=

new

Box

<

String

>();

integerBox

.

add

(

new

Integer

(

10

));

stringBox

.

add

(

new

String

(

"Hello World"

));

System

.

out

.

printf

(

"Integer Value :%d\n\n"

,

integerBox

.

get

());

System

.

out

.

printf

(

"String Value :%s\n"

,

stringBox

.

get

());

}

}

Kết quả nhận được sẽ là.
Integer Value :10
String Value :Hello World

Như vậy ví dụ trên ta tạo một class Box là generic có 2 phương thức là add và get .Khi ta sử dụng Generic Box trong hàm main ( 58,59) . Tuỳ vào ngữ cảnh mà Generic Box có thể chứa kiểu đối tượng Integer (Box ) hay nó
chứa kiểu dữ liệu là String (Box\<String\>) . Dù kiểu dữ liệu Integer hay String ta đều sử dụng được phương thức get và set đã được định nghỉa trong lớp Generic Box. Như vậy mình thấy
sử dụng Generic mình đỡ phải viết code nhiều. Mình có thể tái sử dụng code cho các đối tượng khác nhau.

2. Cách tạo Generic method

Ví dụ ta viết một phương thức in tất cả các phần tử là Generic. Mọi người chú ý tham số truyền vào trong phương thức là chữ <E> đó là tham khi ta muốn viết một hàm generic.
Tuỳ vào tham số truyền vào là kiểu dữ liệu gì . Ta cũng in được các phần tử con trong tập hợp đó
Ví dụ ta viết phương thức printArrayGeneric sau truyền vào tham số là một kiểu generic. Ký tự <E> ta sẽ bàn trong phần tiếp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   

public

class

GenericMethodTest

{

// generic method printArrayGeneric

public

static

<

E

>

void

printArrayGeneric

(

E

[]

inputArrayGeneric

)

{

// Display array elements

for

(

E

elementGeneric

:

ArrayGeneric

)

{

System

.

out

.

printf

(

"%s "

,

elementGeneric

);

}

System

.

out

.

println

();

}

public

static

void

main

(

String

args

[])

{

// Create arrays of Integer, Double and Character

Integer

[]

intArrayGeneric

=

{

2

,

4

,

6

,

8

,

10

};

Double

[]

doubleArrayGeneric

=

{

2.1

,

3.2

,

4.3

,

5.4

};

Character

[]

charArrayGeneric

=

{

'L'

,

'E'

,

'V'

,

'U'

,

'O'

};

System

.

out

.

println

(

"Array intArrayGeneric contains:"

);

printArrayGeneric

(

intArrayGeneric

);

// pass an Integer array

System

.

out

.

println

(

"\nArray doubleArray contains:"

);

printArrayGeneric

(

doubleArrayGeneric

);

// pass a Double array

System

.

out

.

println

(

"\nArray characterArray contains:"

);

printArrayGeneric

(

charArrayGeneric

);

// pass a Character array

}

}

Kết quả nhận được sẽ là.

Array intArrayGeneric contains:
2 4 6 8 10

Array doubleArrayGeneric contains:
2.1 3.2 4.3 5.4

Array characterArray contains:
L E V U O

Như vậy ở ví dụ trên ta tạo ra một phương thức in ra màn hình là generic . Tuỳ thuộc vào đối số truyền vào là Integer , String, hay Double thì phương thức in đều in ra được các phần tử
Nếu ta truyền đối số là Integer thì sẽ nhận được kết quả là các số nguyên trong tập hợp được in ra . Nếu ta truyền đối số là Double thì ta sẽ nhận được các số thực được in ra . Như vậy ta chỉ viết code 1 lần và sử dụng được cho tất
cả các đối số là những kiểu dữ liệu khác nhau.

3. Các ký tự trong Generic

Như ta thấy ở các ví dụ trên ta dùng các ký tự đặt biệt như <T> hay <E> để đặt tên các kiểu dữ liệu và tham số. Ta có thể dùng các từ khác cũng được như X,Y,Z . Nhưng do <T> hay <E>
là các qui ước chung cho các lập trình viên đọc cho dể hiểu, dể bảo trì nên ta không nên đặt các từ khác gây nhầm lẫn. Chúng ta có các qui ước sau.

  • E- Element (Phần tử như Student , Teacher)
  • K – Key (Giống như key trong tập hợp Map)
  • V – Value (V là giá trị giống như kiểu Value trong M )
  • N – Number (Kiểu số)
  • T – Type (Loại đối tượng ví dụ như con chó , gà , mèo thuộc loại động vật)

4. Generic với các ký tự đại diện

Trong Generic nhiều lúc chúng ta sẽ gặp các ký tự đại diên như : (?),(wildcard), nó đại diện cho một loại dữ liệu không rõ ràng.

  • Collection<?>
  • List<? extends Number>
  • Comparator<? super String>
  • Pair<String,?>.
  1. Ký tự đại diện <?> chấp nhận tất cả các loại đối số (chứa mọi kiểu đối tượng).
    Ví dụ: Collection<?> mô tả một tập hợp chấp nhận tất cả các loại đối số kiểu String, Integer, Boolean, …

  2. Ký tự đại diện <? extends type>: Các đối tượng bất kỳ nào cũng được nhưng bắt buộc phải có cùng kiểu dữ liệu mới hợp lệ .
    Ví dụ: List<? extends Number> mô tả một danh sách, nơi mà các phần tử là kiểu Number hoặc kiểu con của Number.

  3. Ký tự đại diện <? super type> chấp nhận bất ký đối tượng nào miễn là đối tượng này là cha của type hoặc đối tượng của type.

5. Generic trong abstract và intefacer

Trong lập trình chúng ta thường sử dụng nhiều generic trong AbstractInterface để code trở nên gọn hơn tái sử dụng được nhiều lần.

1. Generic trong Abstract được khai báo như sau

1
2
3
4

abstract

class

Animal

<

T

>

{

protected

abstract

<

T

>

getAnimalName

();

}

2. Generic trong Interface được khai báo như sau

1
2
3
4
5
6
7

public

interface

GenericDao

<

T

>

{

void

insert

(

T

obj

);

void

update

(

T

obj

);

}

6. Lợi ích khi dùng generic

  • Kiểu dữ liệu an toàn: Chúng ta chỉ có thể giữ được một loại đối tượng trong Generics. Nó không cho phép lưu trữ các loại đối tượng khác.
  • Kiểm tra dữ liệu chặt chẽ ở Compile-time mà không phải là Runtime-error. Nên chúng ta sẽ dễ dàng kiểm soát lỗi hơn.
  • Hạn chế việc ép kiểu (cast) thủ công mà không an toàn.
  • Giúp chúng ta viết các thuật toán được sử dụng nhiều (reusable), dễ dàng thay đổi, an toàn dữ liệu và dễ đọc hơn.

7. Nhược điểm

  • Không thể gọi Generics bằng kiểu dữ liệu nguyên thủy (Primitive type: int, long, double, …), thay vào đó sử dụng các kiểu dữ liệu Object.
  • Không thể tạo instances của kiểu dữ liệu Generics.
  • Không thể sử dụng static cho Generics.

Và bây giờ, hãy cùng xem code demo ở bên dưới để hiểu rõ hơn nhé .

Mọi người hãy Subscribe kênh youtube dưới đây nhé để cập nhật các video mới nhất về kỹ thuật và kỹ năng mềm

Các khoá học lập trình MIỄN PHÍ tại đây