Generic trong java (Bài 6) – Gia Sư Tin Học

Generic trong java sẽ bắt đầu với khái niệm Generics. Thuật ngữ “Generics” nghĩa là tham số hóa kiểu dữ liệu. Tham số hóa kiểu dữ liệu rất quan trọng vì nó cho phép chúng ta tạo ra và sử dụng một class, interface, method với nhiều kiểu dữ liệu khác nhau.

Một class, interface hay một method mà thực hiện trên một kiểu tham số xác định thì gọi là generic.

Generics là cách thức lập trình tổng quát cho phép một object hoạt động với nhiều kiểu dữ liệu khác nhau.

Generic trong java – Một số ví dụ

Ví du 1: Tạo class tên SampleGeneric1 trong package generic và sử dụng ArrayList với các kiểu dữ liệu khác nhau

package generic;
import java.util.ArrayList;
/**
 *
 * @author giasutinhoc.vn
 */
public class SampleGeneric1 {
 public static void main(String[] args) {
   // Sử dụng ArrayList với các kiểu dữ liệu khác nhau
   ArrayList mylist = new ArrayList();
   // Thêm vào array
   mylist.add(10);
   mylist.add("Hello");
   mylist.add(true);
   mylist.add(15.75);
   // Lấy ra
   int i = (Integer)mylist.get(0);
   String s = (String)mylist.get(1);
   boolean b = (boolean)mylist.get(2);
   double d = (double)mylist.get(3);
   // Hiển thị
   System.out.println("Phan tu thu nhat la: " + i);
   System.out.println("Phan tu thu hai la: " + s);
   System.out.println("Phan tu thu ba la: " + b);
   System.out.println("Phan tu thu tu la: " + d);
 }
}

Ví dụ 2: Sử dụng ArrayList với các kiểu dữ liệu Integer

generic trong java 1

Tạo class tên SampleGeneric2 trong package generic

package generic;
import java.util.ArrayList;
/**
 *
 * @author giasutinhoc.vn
 */
public class SampleGeneric2 {
 public static void main(String[] args) {
   // Sử dụng ArrayList với các kiểu dữ liệu Integer
   ArrayList<Integer> mylist = new ArrayList<Integer>();
   // Thêm vào array
   mylist.add(10);
   mylist.add("Hello");  //Error
   // Lấy ra
   int i = mylist.get(0);
   // Hiển thị
   System.out.println("So nguyen: " + i);
  }
}

Ví dụ 3: Sử dụng ArrayList với các kiểu dữ liệu String

Tạo class tên SampleGeneric3 trong package generic

package generic;
import java.util.ArrayList;
/**
 *
 * @author giasutinhoc.vn
 */
public class SampleGeneric3 {
 public static void main(String[] args) {
   // Sử dụng ArrayList với các kiểu dữ liệu String
   ArrayList<String> mylist = new ArrayList<String>();
   // Thêm vào array
   mylist.add("Nguyen Thi Xuan");
   mylist.add("Tran Van Ha");
   // Lấy ra
   String name1 = mylist.get(0);
   String name2 = mylist.get(1);
   // Hiển thị
   System.out.println("Ho ten nguoi thu nhat: " + name1);
   System.out.println("Ho ten nguoi thu hai: " + name2);
  }
}

Generic trong java – Ưu điểm của generic

Kiểm tra kiểu dữ liệu trong thời điểm biên dịch

Trình biên dịch Java áp dụng việc kiểm tra đoạn mã generic
để phát hiện các vấn đề như vi phạm an toàn kiểu dữ liệu. Việc sửa lỗi
tại thời gian biên dịch dễ dàng hơn nhiều khi sửa chữa lỗi tại thời điểm
chạy chương trình.

Không cần ép kiểu dữ liệu: đoạn code sau đây không dùng generic nên phải ép kiểu

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);      //phải ép kiểu

Khi dùng generic, việc ép kiểu đã được loại bỏ:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   //không ép kiểu

Cho phép lập trình viên thực hiện các xử lý tổng quát.

Bằng cách sử dụng generics, người lập trình có thể thực hiện
các thuật toán tổng quát với các kiểu dữ liệu tùy chọn khác nhau, và
nội dung đoạn code trở nên rõ ràng và dễ hiểu.

Generic trong java – Generic Methods

Là phương pháp giúp tạo ra một phương thức mà có thể được gọi với nhiều kiểu dữ liệu khác nhau. Dựa vào kiểu dữ liệu được truyền vào, trình biên dịch sẽ xử lý mỗi lời gọi phương thức sao cho phù hợp.

Quy ước đặt tên tham số kiểu cho Generics

tựÝ nghĩaEElement – phần tửKKey – khóaVValue – giá trịTType – kiểu dữ liệuNNumber – số

Ví dụ tạo class tên GenericMethodTest trong package generic

package generic;
/**
 *
 * @author giasutinhoc.vn
 */
public class GenericMethodTest {
 // generic method printArray
 public <T> void printArray(T[] inputArray) {
  // Display array elements
   for ( T element : inputArray ){
    System.out.print(element);
   }
   System.out.println();
 }
 public static void main( String args[] ){
   // Create arrays of Integer, Double and Character
   Integer[] intArray = { 1, 2, 3, 4, 5 };
   Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
   Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
   // Create object
   GenericMethodTest gmt = new GenericMethodTest();
   System.out.println( "Array integerArray contains:" );
   gmt.printArray( intArray ); // pass an Integer array
   System.out.println( "\nArray doubleArray contains:" );
   gmt.printArray( doubleArray ); // pass a Double array
   System.out.println( "\nArray characterArray contains:" );
   gmt.printArray( charArray ); // pass a Character array
 }
}

Chạy chương trình sẽ cho kết quả như sau:

generic trong java 2

Generic trong java – Generic Classes

Khai báo một lớp dạng generic giống như khai báo một lớp không phải generic ngoại trừ theo sau tên lớp là một kiểu dữ liệu tổng quát.

Giống như phương thức dạng generic, trong trường hợp có nhiều tham số thì mỗi tham số được phân cách bởi dấu phẩy.

Ví dụ tạo class tên Box trong package generic

package generic;
/**
 *
 * @author giasutinhoc.vn
 */
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> intBox = new Box<Integer>();
  Box<String> strBox = new Box<String>();
  intBox.add(new Integer(10));
  strBox.add(new String("Hello World"));
  System.out.println("Integer Value: " + intBox.get());
  System.out.println("String Value: " + strBox.get());
 }
}

Kết quả sau khi chạy chương trình là:

Integer Value :10 
String Value :Hello World

Generic trong java – Các ký tự đại diện generic (Wildcards)

Ký tự đại diện <?>

Xét ví dụ sau:

private boolean checkEquals(RestricExample<T> e) {
 if(number.doubleValue() == e.number.doubleValue()) {
   return true;
 }
 return false;
}
 private boolean checkEquals2(RestricExample<?> e) {
  if(number.doubleValue() == e.number.doubleValue()) {
   return true;
  }
  return false;
 }

Phương thức checkEquals chỉ chấp nhận
tham số e có kiểu dữ liệu giống với biến number. Phương thức
checkEquals2 chấp nhận tham số e có kiểu dữ liệu khác với biến number.

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ụ:

public void processElement(List<? extends A> elements){
 ...
}

Trong đó:

Sử
dụng phương thức processElement (Chấp nhận bất ký đối tượng nào miễn là
đối tượng này phải kế thừa từ lớp A hoặc đối tượng của A => ClassA,
ClassB và ClassC)

List<A> listA = new ArrayList<A>();
processElement(listA);

List<B> listB = new ArrayList<B>();
processElement(listB);

List<C> listC = new ArrayList<C>();
processElement(listC);

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ụ:

public void processElement(List<? super A> elements){
 ...
}
List<A> listA = new ArrayList<A>();
processElement(listA);

List<Object> listO = new ArrayList<Object>();
processElement(listO);

Generic trong java – Giới hạn kiểu dữ liệu với ký tự đại diện <? extends type>

Có một số trường hợp chúng ta muốn hạn
chế kiểu dữ liệu của các tham số. Chẳng hạn tạo phương thức chỉ chấp
nhận tham số với kiểu dữ liệu là số nguyên hoặc số thực.

package generic;
/**
 *
 * @author giasutinhoc.vn
 */
public class RestricExample <T extends Number> {
 private T number;
 public RestricExample(T number) {
  this.number = number;
 }

 public double reciprocal() {
   return 1/number.doubleValue();
 }

 public static void main(String[] args) {
   RestricExample<Integer> n1 = new RestricExample<Integer>(5);
   System.out.println("Reciprocal: " + n1.reciprocal());
   RestricExample<Double> n2 = new RestricExample<Double>(7.5);
   System.out.println("Reciprocal: " + n2.reciprocal());
   //error
   //RestricExample<String> s1 = new RestricExample<String>("Hello");
 }
}

Xây dựng một phương thức xử lý với nhiều kiểu dữ liệu khác nhau.

Ví dụ tạo class tên MaximumTest trong package generic

package generic;
/**
 *
 * @author giasutinhoc.vn
 */
public class MaximumTest {
 // determines the largest of three Comparable objects
 public <T extends Comparable<T>> T maximum(T x, T y, T z) {
   T max = x; // assume x is initially the largest

   if (y.compareTo(max) > 0) {
     max = y; // y is the largest so far
   }

   if (z.compareTo(max) > 0) {
    max = z; // z is the largest now
   }

   return max; // returns the largest object
 }

 public static void main(String args[]) {
   // Create object
   MaximumTest mt = new MaximumTest();
   System.out.println("Max of 3, 4, 5 is " + mt.maximum(3, 4, 5));
   System.out.println("Maxm of 6.6, 8.8, 7.7 is " + mt.maximum(6.6, 8.8, 7.7));
   System.out.println("Max of pear, apple, orange is " + mt.maximum("pear", "apple", "orange"));
 }

Kết quả khi sau khi chạy là:

Max of 3, 4, 5 is 5
Max of 6.6, 8.8, 7.7 is 8.8
Max of pear, apple, orange is pear

Một số hạn chế của Generic

Không thể khởi tạo generic với dữ liệu kiểu nguyên thủy

Pair<int, char> p = new Pair<>(8, 'a'); //error
Pair<Integer, Character> p = new Pair<>(8, 'a');

Không thể tạo instance cho kiểu dữ liệu

class Gen<T> {
 T obj;

 Gen() {
   obj= new T(); //Illegal (error)
 }
}

Không thể là static trong class

class Gen<T> {
 static T obj; //Kiểu T không thể là static
 static T getObj() { //Phương thức không thể static
   return obj;
 }
}

Không thể tạo mảng

Gen<Integer> gens[] = new Gen<Integer>[10]; //error
Gen<?> gens[] = new Gen<?>[10]; //ok
Gens[0] = new Gen<Integer>(25);
Gens[1] = new Gen<String>("Hello");

Không thể tạo class ngoại lệ là generic

Generic trong java – Bài tập thực hành

Bài thực hành số 1: Tạo class tên MyArrayList và thực hiện các công việc sau:

  • Thêm vào ArrayList một số nguyên
  • Thêm vào ArrayList một số thực
  • Thêm vào ArrayList một giá trị boolean
  • Thêm vào ArrayList một chuỗi
  • In ra màn hình 4 giá trị trên

Bài thực hành số 2: Tạo class tên MyGenericArrayList và thực hiện các công việc sau:

  • Tham số hoá dữ liệu cho ArrayList là Integer.
  • Sử dụng vòng lặp để nhập các số từ 1 đến 10 vào ArrayList.
  • In ra màn hình các giá trị trong ArrayList

Bài thực hành số 3: Tạo class tên Student có các thuộc tính như id, name, age. Viết các phương thức constructor, setter, getter, toString.

Bài thực hành số 4: Tạo class tên Employee có các thuộc tính như id, name, salary. Viết các phương thức constructor, setter, getter, toString.

Bài thực hành số 5: Tạo class PersonModel và thực hiện các công việc sau:

package generic;
import java.util.ArrayList;
/**
 *
 * @author giasutinhoc.vn
 */
public class PersonModel <T> {

  private ArrayList<T> al = new ArrayList<T>();

  public void add(T obj) {
   al.add(obj);
  }

  public void display() {
   for (T o : al) {
    System.out.println(o);
   }
  }

  public static void main(String[] args) {
   //Viết xử lý cho phương thức main
  }
}

Đoạn code cần viết thêm vào phương thức main để thực hiện các công việc sau:

Tạo đối tượng PersonModel<Student>

  • Gọi phương thức add để nhập vào 2 sinh viên
  • Gọi phương thức display để hiển thị thông tin của 2 sinh viên vừa nhập

Tạo đối tượng PersonModel<Employee>

  • Gọi phương thức add để nhập vào 2 nhân viên
  • Gọi phương thức display để hiển thị thông tin của 2 nhân viên vừa nhập

Tạo đối tượng PersonModel<String>

  • Gọi phương thức add để nhập vào họ tên của 2 người.
  • Gọi phương thức display để hiển thị họ tên vừa nhập.