GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Java Core Collection Hướng dẫn sử dụng Java Generics

Hướng dẫn sử dụng Java Generics

Đăng vào 02/12/2017 . Được đăng bởi GP Coder . 35825 Lượt xem . Toàn màn hình

Generics là một tính năng của Java giúp cho lập trình viên có thể chỉ định rõ kiểu dữ liệu mà họ muốn làm việc với một class, một interface hay một phương thức nào đó. Trong bài viết này, chúng ta sẽ cùng tìm hiểu về Generics trong Java.

Nội dung

  • 1 Tại sao lại cần có Generics?
  • 2 Một số quy ước đặt tên kiểu tham số Generic
  • 3 Ký tự Diamond <>
  • 4 Kiểu Generic cho Class và Interface
  • 5 Phương thức generics
  • 6 Khởi tạo đối tượng Generic
  • 7 Mảng Generic
  • 8 Generics với ký tự đại diện
  • 9 Ưu điểm của Generics
  • 10 Một số hạn chế khi sử dụng Generics

Tại sao lại cần có Generics?

Generics là một khái niệm được đưa vào Java từ phiên bản 5. Trước khi đưa ra khái niệm Generics là gì, chúng ta hãy xem một đoạn code của Java trước phiên bản 5.

Như bạn đã biết ArrayList là một danh sách, bạn có thể thêm, xóa, sửa và truy cập vào các phần tử của danh sách.


List list = new ArrayList();

Với khai báo trên, giả định rằng chúng ta mong muốn chỉ làm việc với đối tượng kiểu Integer. Nhưng bởi vì list là một collection của đối tượng Object nên chúng ta có thể sử dụng nó với bất kỳ kiểu dữ liệu nào. Tại nơi nào đó trong chương trình bạn thêm vào danh sách này một phần tử không phải Integer. Khai báo sau sẽ hợp lệ:


list.add(10);
list.add("gpcoder.com");
list.add(true);

Như bạn thấy, tôi có thể thêm các phần tử kiểu Integer, String, Boolean. Tuy nhiên, khi bạn lấy ra các phần tử và ép kiểu về Integer, một ngoại lệ sẽ bị ném ra.

Đó là nguyên nhân của sự cần thiết phải có của generics trong Java. Với Generics, chúng ta có thể chỉ định kiểu dữ liệu mà chúng ta sẽ làm việc ngay thời điểm biên dịch (compile time).

Ví dụ trên có thể viết lại như sau:


List<Integer> list = new ArrayList<Integer>();

Khi thêm một phần tử không phải kiểu Integer trình biên dịch sẽ báo lỗi ngay:

 

Một số quy ước đặt tên kiểu tham số Generic

Đặt tên kiểu tham số là rất quan trọng để học Genericics. Nó không bắt buộc, tuy nhiên chúng ta nên đặt theo quy ước chung để dễ đọc, dễ bảo trì. Các kiểu tham số thông thường như sau:

  • E- Element (phần tử – được sử dụng phổ biến trong Collection Framework)
  • K – Key (khóa)
  • V – Value (giá trị)
  • N – Number (kiểu số: Integer, Double, Float, …)
  • T – Type (Kiểu dữ liệu bất kỳ thuộc Wrapper class: String, Integer, Long, Float, …)
  • S, U, V … – được sử dụng để đại diện cho các kiểu dữ liệu (Type) thứ 2, 3, 4, …

Ký tự Diamond <>

Trong Java 7 và các phiên bản sau, bạn có thể thay thế các đối số kiểu dữ liệu cần thiết để gọi hàm khởi tạo (constructor) của một lớp Generic bằng cặp dấu <>. Trình biên dịch sẽ xác định hoặc suy ra các kiểu dữ liệu từ ngữ cảnh sử dụng.

Ví dụ, bạn có thể tạo một list <Integer> với câu lệnh sau:

// Trước Java 7
List<Integer> integerBox = new ArrayList<Integer>();

// Khai báo sử dụng cặp dấu <> từ phiên bản Java 7 
List<Integer> integerBox = new ArrayList<>();

Để biết thêm thông tin về ký hiệu <>, bạn xem thêm trên trang document của Oracle.

Kiểu Generic cho Class và Interface

Kiểu Generic cho Class

Ví dụ dưới đây định nghĩa ra một class Generics. KeyValuePair là một class Generics nó chứa một cặp khóa và giá trị (key/ value).


package com.gpcoder.generic;

public class KeyValuePair<K, V> {
	private K key;
	private V value;

	public KeyValuePair(K key, V value) {
		this.key = key;
		this.value = value;
	}

	public K getKey() {
		return key;
	}

	public void setKey(K key) {
		this.key = key;
	}

	public V getValue() {
		return value;
	}

	public void setValue(V value) {
		this.value = value;
	}
}

K, V trong class KeyValuePair<K, V> được gọi là tham số Generics nó là một kiểu tham chiếu nào đó. Khi sử dụng class này bạn phải xác định kiểu tham số cụ thể.


package com.gpcoder.generic;

public class KeyValuePairExample {
	public static void main(String[] args) {
		KeyValuePair<String, Integer> entry = new KeyValuePair<String, Integer>("gpcoder", 123456789);
		String name = entry.getKey();
		Integer id = entry.getValue();
		System.out.println("Name = " + name + ", Id = " + id); // Name = gpcoder, Id = 123456789
	}
}

Thừa kế lớp Generics

Một class mở rộng từ một class Generics, nó có thể chỉ định rõ kiểu cho tham số Generics, giữ nguyên các tham số Generics hoặc thêm các tham số Generics.


package com.gpcoder.generic;

public class ContactEntry extends KeyValuePair<String, Integer> {

	public ContactEntry(String key, Integer value) {
		super(key, value);
	}

}

Ví dụ sử dụng ContactEntry:


package com.gpcoder.generic;

public class ContactEntryExample {
	public static void main(String[] args) {
		ContactEntry entry = new ContactEntry("gpcoder", 123456789);
		String name = entry.getKey();
		Integer id = entry.getValue();
		System.out.println("Name = " + name + ", Id = " + id); // Name = gpcoder, Id = 123456789
	}
}

Một vài cách sử dụng kế thừa khác:

ContactEntry2.java


package com.gpcoder.generic;

public class ContactEntry2<V> extends KeyValuePair<String, V> {

	public ContactEntry2(String key, V value) {
		super(key, value);
	}

}

ContactEntry3.java


package com.gpcoder.generic;

public class ContactEntry3<K, V> extends KeyValuePair<K, V> {

	public ContactEntry3(K key, V value) {
		super(key, value);
	}

}

ContactEntry4.java


package com.gpcoder.generic;

public class ContactEntry4<K, V, T> extends KeyValuePair<K, V> {

	private T obj;

	public ContactEntry4(K key, V value, T obj) {
		super(key, value);
		this.obj = obj;
	}

	public T getObj() {
		return obj;
	}

	public void setObj(T obj) {
		this.obj = obj;
	}

}

Kiểu Generic cho Interface

Một Interface có tham số Generics:


package com.gpcoder.generic;

public interface GenericDao<T> {

	void insert(T obj);

	void update(T obj);

}

Ví dụ một class cài đặt Interface trên:


package com.gpcoder.generic;

public class GenericDaoImpl<T> implements GenericDao<T> {

	@Override
	public void insert(T obj) {
		// do something
	}

	@Override
	public void update(T obj) {
		// do something
	}

}

Ví dụ 2 class Student và Teacher sử dụng GenericDao trên:

package com.gpcoder.generic;

public class StudentDao extends GenericDaoImpl<Student> {
	
}
package com.gpcoder.generic;

public class TeacherDao extends GenericDaoImpl<Teacher> {
	
}

Ví dụ sử dụng các lớp trên:

package com.gpcoder.generic;

public class GenericDaoExample {

	public static void main(String[] args) {
		Student student = new Student(1, "gpcoder", 28);
		StudentDao dao = new StudentDao();
		dao.insert(student);
	}
}

Phương thức generics

Một phương thức trong class hoặc Interface có thể sử dụng generic.


package com.gpcoder.generic;

import java.util.Collection;

public class MyUtils {
	public static <T> int count(Collection<T> collection, T itemToCount) {
		int count = 0;
		for (T item : collection) {
			if (itemToCount.equals(item)) {
				count++;
			}
		}
		return count;
	}
}

Ví dụ sử dụng phương thức Generics:

package com.gpcoder.generic;

import java.util.ArrayList;
import java.util.List;

public class MyUtilsExample {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("A");
		list.add("B");
		list.add("C");
		list.add("A");
		list.add("C");
		System.out.println(MyUtils.count(list, "A")); // 2
	}
}

Khởi tạo đối tượng Generic

Đôi khi bạn muốn khởi tạo một đối tượng Generic:


T obj = new T(); // Error

Việc khởi tạo một đối tượng generic như trên là không được phép, vì <T> không hề tồn tại ở thời điểm chạy của Java. Nó chỉ có ý nghĩa với trình biên dịch kiểm soát code của người lập trình. Mọi kiểu <T> đều như nhau nó được hiểu là Object tại thời điểm chạy của Java.
Muốn khởi tạo đối tượng generic <T> bạn cần cung cấp cho Java đối tượng Class<T>, Java sẽ tạo đối tượng <T> tại thời điểm runtime bằng Java Reflection.


package com.gpcoder.generic;

public class GenericInstance<T> {
	private T obj;

	public GenericInstance(Class<T> aClazz) 
			throws InstantiationException, IllegalAccessException {
		this.obj = (T) aClazz.newInstance();
	}

	public T getObj() {
		return obj;
	}
}

Ví dụ sử dụng phương thức khởi tạo trên:



package com.gpcoder.generic;

public class GenericInstanceExample {
	public static void main(String[] args) 
			throws InstantiationException, IllegalAccessException {

		GenericInstance<Student> generic = new GenericInstance<Student>(Student.class);
		Student student = generic.getObj();
		System.out.println(student);
		
	}
}

Mảng Generic

Có thể khai báo một mảng Generic, nhưng không thể khởi tạo một mảng Generic. Vì kiểu generic không hề tồn tại tại thời điểm chạy, List<String> hoặc List<Integer> đều là List. Generic chỉ có tác dụng với trình biên dịch để kiểm soát code của người lập trình. Điều đó có nghĩa là trình biên dịch của Java cần biết rõ <T> là cái gì mới có thể biên dịch (compile) new T[10];. Nếu không biết rõ nó mặc định coi T là Object.


T[] arr; // Ok

T[] arr2 = new T[5]; // Error

Ví dụ:


package com.gpcoder.generic;

public class GenericArray<T> {
	private T[] array;

	// Contructor.
	public GenericArray(T[] array) {
		this.array = array;
	}

	public T[] getArray() {
		return array;
	}

	// Trả về phần tử cuối cùng của mảng.
	public T getLastElement() {
		if (this.array == null || this.array.length == 0) {
			return null;
		}
		return this.array[this.array.length - 1];
	}
}

Chương trình sử dụng GenericArray:


package com.gpcoder.generic;

public class GenericArrayExample {
	public static void main(String[] args) {
		// Một mảng các String.
		String[] names = new String[] { "Tom", "Jerry" };

		GenericArray<String> gArray = new GenericArray<String>(names);

		String last = gArray.getLastElement();

		System.out.println("Last Element = " + last);
	}
}

Quay trở lại với vấn đề tại sao Java không hỗ trợ khởi tạo một mảng Generic?

Lý do là kiểu generic không hề tồn tại tại thời điểm chạy, List<String> hoặc List<Integer> đều là List. Generic chỉ có tác dụng với trình biên dịch để kiểm soát code của người lập trình. Điều đó có nghĩa là trình biên dịch của Java cần biết rõ <T> là cái gì mới có thể biên dịch (compile) new T[10];. Nếu không biết rõ nó mặc định coi T là Object.

Nếu muốn khởi tạo mảng Generic bạn cần phải truyền cho Java đối tượng Class<T>, giúp Java có thể khởi tạo mảng generic tại thời điểm runtime bằng cách sử dụng Java Reflection. Hãy xem ví dụ minh họa:


package com.gpcoder.generic;

import java.lang.reflect.Array;

public class GenericArrayContructor<T> {
	private final int size = 10;
	private Class<T> aClazz;

	private T[] myArray;

	public GenericArrayContructor(Class<T> aClazz) {
		this.aClazz = aClazz;
		myArray = (T[]) Array.newInstance(aClazz, size);
	}

	public T[] getMyArray() {
		return this.myArray;
	}
}

Chương trình sử dụng GenericArrayContructor trên:


package com.gpcoder.generic;

public class GenericArrayContructorExample {
	public static void main(String[] args) {
		GenericArrayContructor<Integer> generic = new GenericArrayContructor<Integer>(Integer.class);
		Integer[] myArray = generic.getMyArray();
		myArray[0] = 1;
		myArray[2] = 0;
	}
}

Generics với ký tự đại diện

Trong mã Generic, dấu chấm hỏi (?), được gọi là một đại diện (wildcard), nó đại diện cho một loại không rõ ràng. Một kiểu tham số đại diện (wildcard parameterized type) là một trường hợp của kiểu Generic, nơi mà ít nhất một kiểu tham số là wildcard.

Ví dụ của tham số đại diện (wildcard parameterized) là :

  • Collection<?>
  • List<? extends Number>
  • Comparator<? super String>
  • Pair<String,?>.

Các ký tự đại diện có thể được sử dụng trong một loạt các tình huống: như kiểu của một tham số, trường (field), hoặc biến địa phương; đôi khi như một kiểu trả về (Sẽ được nói rõ hơn trong các ví dụ thực hành). Các đại diện là không bao giờ được sử dụng như là một đối số cho lời gọi một phương thức Generic, khởi tạo đối tượng class generic, hoặc kiểu cha (supertype).

Các ký hiệu đại diện nằm ở các vị trí khác nhau có ý nghĩa khác nhau:

  • 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, …
  • Ký tự đại diện <? extends type>: chấp nhận bất ký đối tượng nào miễn là đối tượng này kế thừa từ type hoặc đối tượng của type. 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.
  • 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. Ví dụ: Comparator<? super String> Mô tả một bộ so sánh (Comparator) mà thông số phải là String hoặc cha của String.

Một kiểu tham số ký tự đại diện không phải là một loại cụ thể để có thể xuất hiện trong một toán tử new. Nó chỉ là gợi ý các quy tắc thực thi bởi Generics java rằng những loại có giá trị trong bất kỳ tình huống cụ thể mà các kí hiệu đại diện đã được sử dụng.

Ví dụ:

Collection<?> coll = new ArrayList<String>();

// Một tập hợp chỉ chứa kiểu Number hoặc kiểu con của Number
List<? extends Number> list = new ArrayList<Long>();

// Một đối tượng có kiểu tham số đại diện.
// (A wildcard parameterized type)
Pair<String,?> pair = new Pair<String,Integer>();

Một số khai báo không hợp lệ:

// String không phải là kiểu con của Number, vì vậy lỗi.
List<? extends Number> list = new ArrayList<String>();

// String không phải là kiểu cha của Integer vì vậy lỗi
ArrayList<? super String> cmp = new ArrayList<Integer>();

Ví dụ với kiểu đại diện (wildcard)

WildCardExample1.java

package com.gpcoder.generic;

import java.util.ArrayList;

public class WildCardExample1 {

	public static void main(String[] args) {

		// Một danh sách chứa các phần tử kiểu String.
		ArrayList<String> listString = new ArrayList<String>();

		listString.add("Tom");
		listString.add("Jerry");

		// Một danh sách chứa các phần tử kiểu Integer
		ArrayList<Integer> listInteger = new ArrayList<Integer>();

		listInteger.add(100);

		// Bạn không thể khai báo:
		// ArrayList<Object> list1 = listString; // ==> Error!

		// Một đối tượng kiểu tham số đại diện.
		// (wildcard parameterized object).
		ArrayList<? extends Object> list2;

		// Bạn có thể khai báo:
		list2 = listString;

		// Hoặc
		list2 = listInteger;

	}

}

WildCardExample2.java

package com.gpcoder.generic;

import java.util.ArrayList;
import java.util.List;

public class WildCardExample2 {

	public static void main(String[] args) {

		List<String> names = new ArrayList<String>();
		names.add("Tom");
		names.add("Jerry");
		names.add("Donald");

		List<Integer> values = new ArrayList<Integer>();
		values.add(100);
		values.add(120);

		System.out.println("--- Names --");
		printElement(names);

		System.out.println("-- Values --");
		printElement(values);

	}

	public static void printElement(List<?> list) {
		for (Object e : list) {
			System.out.println(e);
		}
	}

}

Đối tượng đại diện không thể sử dụng phương thức generic

Wildcard không thể tham gia trong toán tử new

Một kiểu tham số ký tự đại diện (wildcard parameterized type) không phải là một loại cụ thể, và nó không thể xuất hiện trong một toán tử new.


// Tham số Wildcard không thể tham gia trong toán tử new.
List<? extends Object> list= new ArrayList<? extends Object>();

Ưu điểm của Generics

Trên đây là những kiên thức cơ bản về Generic, tôi xin tổng hợp lại các ưu điểm của Generic như sau:

  • 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. Nó rất hữu ích cho những người viết software libraries (thư viện phần mềm) làm sao để generic programming (lập trình có tính tổng quát) vì nó cho phép người dùng sử dụng ở nhiều tình huống khác nhau.

Một số hạn chế khi sử dụng Generics

  • 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 (wrapper class thay thế: Integer, Long, Double, …).
  • Không thể tạo instances của kiểu dữ liệu Generics, thay vào đó sử dụng reflection từ class (xem ví dụ ở trên).
  • Không thể sử dụng static cho Generics.

private static T obj; // compile-time error

  • Không thể ép kiểu hoặc sử dụng instanceof.

public static <E> void rtti(List<E> list) {
     if (list instanceof ArrayList<Integer>) {  // compile-time error
     // ...
     }
}

List<Integer> li = new ArrayList<Integer>();
List<Number>  ln = (List<Number>) li;  // compile-time error

  • Không thể tạo mảng với parameterized types (như đã nói ở phần trên).
  • Không thể tạo, catch, throw đối tượng của parameterized types (Generic Throwable). Vì thông tin Generic chỉ sử dụng cho trình biên dịch kiểm soát code của người lập trình. Trong thời điểm chạy Java thông tin Generic không hề tồn tại.

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

  • Không thể overload các hàm trong một lớp giống như:

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

Tài liệu tham khảo:

  • https://docs.oracle.com/javase/tutorial/java/generics/index.html
  • http://o7planning.org/vi/10403/huong-dan-su-dung-java-generics
5.0
15
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Collection Được gắn thẻ: Collection, Generic

Chuyển đổi Array sang ArrayList và ngược lại
Hướng dẫn sử dụng Java Reflection

Có thể bạn muốn xem:

  • Chuyển đổi từ HashMap sang ArrayList (29/11/2017)
  • LinkedList trong java (12/11/2017)
  • Lớp Arrarys trong Java (Arrays Utility Class) (26/11/2017)
  • Vector trong Java (21/11/2017)
  • So sánh Array và ArrayList trong Java (12/11/2017)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (97634 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97319 lượt xem)
  • Giới thiệu Design Patterns (87116 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (85857 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83374 lượt xem)

Nội dung bài viết

  • 1 Tại sao lại cần có Generics?
  • 2 Một số quy ước đặt tên kiểu tham số Generic
  • 3 Ký tự Diamond <>
  • 4 Kiểu Generic cho Class và Interface
  • 5 Phương thức generics
  • 6 Khởi tạo đối tượng Generic
  • 7 Mảng Generic
  • 8 Generics với ký tự đại diện
  • 9 Ưu điểm của Generics
  • 10 Một số hạn chế khi sử dụng Generics

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link