Generics trong Java

[toc]

Định nghĩa

Generic programming trong Java là việc ta cho phép 1 phương thức hay 1 class xử lý các đối tượng thuộc 1 kiểu dữ liệu tuỳ ý (generic). Kiểu dữ liệu tuỳ ý này có dạng  compile-time type safety. Tức là 1 khi nó đã được khai báo, trình biên dịch sẽ không chấp nhận việc phương thức hay class chứa xử lý các kiểu dữ liệu khác.

Để dễ hiểu hơn mình sẽ lấy 1 ví dụ đơn giản thế này. Ta khai báo 1 ArrayList như sau:

ArrayList<String> listName = new ArrayList<>();

Bạn thấy listName là 1 ArrayList và nó chỉ chấp nhận các giá trị String. Ở đây ArrayListclass chứa và String chính là kiểu dữ liệu “tuỳ ý” mà mình nói ở trên.

Mình lại lấý 1 ví dụ khác:

ArrayList<Integer> listNumber = new ArrayList<>();

Lần này thì ArrayList lại chỉ chấp nhận xử lý kiểu dữ liệu Integer, bởi vì Integer đã được khai báo với ArrayList.

Tạo 1 generic class (lớp chứa)

Giống như mọi class khác, generic class được khai báo với từ khoá class. Điểm khác biệt ở đây là nó còn có thêm cặp <T> để tượng trưng cho kiểu dữ liệu mà nó sẽ xử lý. Ví dụ:

public class Param<T> {
	T value;

	public T getValue() {
		return value;
	}

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

Như các bạn thấy <T> tượng trưng cho kiểu dữ liệu sẽ được xử lý bởi Param. Nó có thể là bất cứ kiểu gì: Integer, String, Boolean, Animal, Human, ….. Nhưng 1 khi đã được khai báo, lớp chứa của nó (generic class) sẽ chỉ chấp nhận xử lý kiểu dữ liệu đã được khai báo.

Giờ mình sẽ tạo ra 1 đối tượng Param chuyên xử lý kiểu dữ liệu Integer:

Param<Integer> paramInteger = new Param<>();
paramInteger.setValue(10); // lúc này hàm setValue chỉ chấp nhận giá trị có kiểu Integer
System.out.println(paramInteger.value); //in ra 10

Mình sẽ tạo 1 đối tượng Param khác chuyên xử lý kiểu String:

Param<String> paramString= new Param<>();
paramString.setValue("cungdev.com"); //// lúc này hàm setValue chỉ chấp nhận giá trị có kiểu String
System.out.println(paramString.value); //in ra cungdev.com

Tương tự, bạn có thể khai báo bất cứ kiểu dữ liệu nào để generic class Param xử lý.

Kế thừa generic class

Việc kế thừa generic class thực chất chẳng khác gì kế thừa 1 class bình thường

Ví dụ mình khai báo 1 generic class AbstractParam như sau (lớp này sẽ được kế thừa bởi các lớp khác):

public abstract class AbstractParam<T> {
	protected T value;
	protected abstract void printValue();

}

Không khác gì 1 lớp bình thường ngoài việc khai báo thêm kiểu dữ liệu generic <T> phải không?

Giờ ta sẽ tạo 1 lớp Email kế thừa AbstractParam:

public class Email extends AbstractParam<String>{

	@Override
	protected void printValue() {
		System.out.println("My email is:"+value);
		
	}	

}

Ta đã tạo 1 lớp Email kế thừa AbstracParam<String>, điều này có nghĩa là:

  • Ta đã chỉ định generic type là String
  • Thuộc tính value trong lớp Email sẽ có kiểu là String.

Ta sẽ test 2 điều trên bằng câu lệnh sau:

Email email = new Email();
email.value = "duongtuanvu1111@gmail.com";
email.printValue();//in ra My email is:duongtuanvu1111@gmail.com
email.value = 10; //Khong duoc, vi value bat buoc phai mang kieu String

Giờ mính sẽ khai báo 1 lớp khác là Age:

public class Age extends AbstractParam<Integer>{

	@Override
	protected void printValue() {
		System.out.println("My age is "+value);
		
	}
	
}

Tương tự như class Email, nhưng lần này Age được khai báo là generic class của kiểu Int, có nghĩa là trường value của Age chỉ có thể mang kiểu Int:

Age age = new Age();
age.value = 10;
age.printValue(); //in ra My age is 10
age.value = "Nam"; //khong duoc vi value bat buoc phai mang kieu Int

Mình sẽ lấy thêm 1 ví dụ nữa, ta khai báo class Height như sau:

public class Height<T> extends AbstractParam<T> {

	@Override
	protected void printValue() {
		System.out.println("My height is "+value);
		
	}

}

Ở đây khi định nghĩa Height, ta chưa khai báo rõ kiểu generic (trong 2 trường hợp trước ta đã chỉ ra luôn StringInteger), tức là Height có thể xử lý bất kỳ kiểu dữ liệu gì mà không bị “ép buộc” phải xử lý StringInteger như class EmailAge. Ví dụ:

Height<Float> heightFloat = new Height<>(); //kieu du lieu se duoc xu ly la Float
heightFloat.value = 10f;
heightFloat.printValue(); //in ra My height is 10.0		
Height<Long> heightLong = new Height<>(); //kieu du lieu se duoc xu ly la Long
heightLong.value = 10000L;
heightLong.printValue(); //in ra My height is 10000

Đó là những kiến thức cơ bản về kế thừa generic class, hy vọng mình diễn đạt dễ hiểu.

Khai báo nhiều dữ liệu generic (Multiple type parameters)

Ta không những có thể khai báo kiểu dữ liệu generic cho class xử lý, ta còn có thể khai báo tuỳ thích số lượng các kiểu generic. Ví dụ:

public class MultiGenericParam<T, S> {
	private T firstParam;
	private S secondParam;

	public MultiGenericParam(T firstParam, S secondParam) {
		this.firstParam = firstParam;
		this.secondParam = secondParam;
	}

	public T getFirstParam() {
		return firstParam;
	}

	public void setFirstParam(T firstParam) {
		this.firstParam = firstParam;
	}

	public S getSecondParam() {
		return secondParam;
	}

	public void setSecondParam(S secondParam) {
		this.secondParam = secondParam;
	}
}

Lớp trên sẽ được sử dụng kiểu thế này:

MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);

T , ? super T và ? extends T

Đôi khi ta không nhất thiết phải khai báo generic Type 1 cách chính xác, ? super T? extends T giúp ta điều này. Cụ thể:

  • ? super T tượng trưng cho các lớp là lớp cha của T
  • ? extends T tượng trưng cho các lớp kế thừa T

Để rõ hơn mình sẽ lấy ví dụ như sau. Mình tạo các class:

class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {} 
class GrannySmith extends Apple {}						

public class FruitHelper {
    public void eatAll(Collection<? extends Fruit> fruits) {} 
    public void addApple(Collection<? super Apple> apples) {}
						
}

Giờ ta sẽ sử dụng các câu lệnh sau để thấy rõ được cách sử dụng của T, ? super T? extends T

FruitHelper fruitHelper = new FruitHelper();
List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Apple()); 
// Hợp lệ, do Apple kế thừa Fruit nên Apple chính là 1 Fruit 
//ArrayList fruits chấp nhận xử lý các đối tượng Fruit nên Apple cũng được chấp nhận
Collection<Banana> bananas = new ArrayList<>();
bananas.add(new Banana());
//Chấp nhận do ArrayList bananas chấp nhận xử lý các đối tượng Banana
Collection<Apple> apples = new ArrayList<>();
fruitHelper.addApple(apples); // Allowed
apples.add(new GrannySmith());
//Chấp nhận do GrannySmith kế thừa Apple
Collection<GrannySmith> grannySmithApples = new ArrayList<>();
fruitHelper.addApple(grannySmithApples);
//Ko chấp nhận do fruitHelper chỉ chấp nhận list các đối tượng Apple
//Tuy GrannySmith kế thừa Apple, nó là 1 Apple nhưng chưa chắc 1 Apple là 1 GrannySmith
fruitHelper.eatAll(grannySmithApples);
//Chấp nhân, do GrannySmith là 1 Fruit
Collection<Object> objects = new ArrayList<>();
fruitHelper.addApple(objects);
//Chấp nhận do Apple kế thừa class Object

 

 

One thought on “Generics trong Java”

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *