Cú pháp và các tính năng mới trong Java 5
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java
Phiên bản đầu tiên của java là java 1.0 trừ các bản update của phiên bản thì các phiên bản tiếp theo của java là 1.2, 1.3, 1.4 và bây giờ chúng ta đang nói chuyện về bản 1.5 tuy nhiên Sun không đánh dấu phiên bản của mình bằng việc tăng 0.1 nữa .Giờ đây là bản 5.0 bản tiếp theo sẽ là 6.0,7.0,..Vì vậy bạn không nên ngạc nhiên gì về điều này .Trong bài nói chuyện này chúng ta sẽ nói về các nâng cấp của phiên bản này so với phiên bản trước đó .Một sự thay đổi lớn lao về ngôn ngữ Java 5.0 với nhan đề "The Roar Of The Tiger" (Tiếng gầm của con hổ).

1- Các thay đổi của Java từ phiên bản đầu tiên

  • JDK 1.0
    • Bắt đầu của ngôn ngữ, rất phổ biến
  • JDK1.1
    • Các Inner class, mô hình sự kiện
  • JDK 1.2, 1.3
    • Không có sự thay đổi nào ở mức ngôn ngữ
  • JDK 1.4
    • Tiếp tục xác nhận phong cách theo các phiên bản trước(Những thay đổi không lớn)
  • JDK 5.0
    • Đây là mốc có sự thay đổi to lớn của ngôn ngữ so với bản 1.0

2- Java 5 - Tiếng gầm của con hổ

2.1- Bẩy tính năng cao cấp mới

  • Generics
  • Autoboxing/Unboxing
  • Nâng cấp thêm tính năng cho vòng lặp for ("foreach")
  • Các kiểu liệt kê có tính an toàn (Type-safe enumerations)
  • Varargs
  • Nhập khẩu tĩnh (Static import)
  • Metadata

2.2- Generic

2.2.1- Generics là gì ?

  • Generic trìu tượng trên các kiểu dữ liệu
    • Các Classe,các Interface và các Method có thể Tham số hóa bởi Các kiểu dữ liệu
  • Generics gia tăng thêm tính có thể đọc hiểu được và an toàn hơn cho code chương trình
  • Dòng code an toàn
    • Nếu một chương trình được dịch không có error hoặc warning nào,thì trong quá trình chạy (runtime) nó sẽ không ném ra điều không mong đợi vì sai xót trong việc ép kiểu (ClassCastException)
Mỗi một class,interface nào đó thực tế là một cách định nghĩa một kiểu dữ liệu nào đó .Ví dụ class String mô tả một kiểu dữ liệu kiểu String .Class Number là mô tả một kiểu Number , Integer là một class mở rộng từ class Number thì nó cũng là kiểu Number tuy nhiên nói mạnh hơn nữa thì nó là kiểu Integer .v..v .Trong 1.5 người ta nghĩ ra việc dùng các kiểu để tham số hóa cho một class,interface hoặc một method nào đó,điều này là chưa từng có trong các phiên bản trước đó .Việc tham số hóa như vậy trong các trường hợp cho class hay interface thì được gọi là generic hóa .Ok hãy xem một ví dụ về generic hóa .
//Đoạn code của interface java.util.List trong phiên bản 1.4.2
 interface List{
  void add(Object o);
  Iterator iterator();
 }
//Đoạn code của interface java.util.Iterator trong phiên bản 1.4.2
 interface Iterator{
  Object next();
  boolean hasNext();
 }
// Đoạn code của interface java.util.List trong phiên bản 1.5
 interface List<E> {
   void add(E x);
   Iterator<E> iterator();
  }
// Đoạn code của interface java.util.Iterator trong phiên bản 1.5
interface Iterator<E>{
   E next();
   boolean hasNext();
 }
Trên đây là các đoạn code của interface java.util.List và interface java.util.Iterator đã được viết lại trong phiên bản 1.5. Khác với phiên bản 1.4.2 trước đó .Chúng ta thấy sự có mặt của <E> ngay đằng sau tên của class/interface .Thực tế thì đó chính là một tham số ,tham số hóa class/interface . Trong định nghĩa mới của interface Iterator method E next() chính là một method đã được tham số hóa .
Vậy bạn sẽ đặt ra câu hỏi <E> là cái gì .Trong thực tế bạn có thể thay chữ E đó bởi bất kỳ một chữ cái hoa nào .Nó ám chỉ rằng đó là một kiểu tham chiếu nào đó .Method E next() của interface Iterator sẽ trả về kiểu E đó.Chẳng hạn chữ cái E này được thay thế bởi một kiểu tham chiếu nào đó trong khi bạn khởi tạo một đối tượng java.util.List :

java.util.List<Number> list=new java.util.ArrayList<Number>();
 
Chữ E đã được thay thế bởi Number (một kiểu loại tham chiếu) .Và giờ đây đối tượng List list chỉ có thể chứa các kiểu Number !!!. Một class/interface có thể generic hóa bởi một hoặc nhiều tham số. Ok cho đến bây giờ bạn nên bằng lòng với các hiểu biết đó.

2.2.2- Generics không là những gì ?

  • Generics không phải là một khuôn mẫu (templates) (cho toàn bộ các class,interface)
  • Không giống như C++, những khai báo generic là kiểu đã kiểm tra
  • Generics được dịch một lần cho tất cả
  • Mã nguồn của Generic không phơi ra cho người dùng nhìn thấy
  • No bloat required

 
Generic không phải là một khuôn mẫu để áp dụng cho tất cả các class/interface .Trong thực tế thì nó chỉ dùng để áp dụng cho các kiểu có tính chất tập hợp với mục tiêu rằng các kiểu tập hợp đó chỉ chứa các kiểu định rõ bởi tham số E ,F...Trong bó java.util có rất nhiều các class/interface mô tả các tập hợp và chúng đã được viết lại trên khuôn mẫu generic .

2.2.3- Sử dụng thế nào (Gọi hoặc khai báo) một class Generic

  • Dưới đây là một minh họa sử dụng LinkedList<E>
  • Thay thế thông số <E> với một đối số cụ thể, như <Integer> hoặc <MyType>
  • LinkedList<E> có một thông số E mô tả một kiểu dữ liệu của các phần tử lưu trữ trong danh sách LinkedList
    • LinkedList<Integer> có thể lưu trữ chỉ Integer hoặc các kiểu con của Integer như các thành viên
     LinkedList<Integer> li =new LinkedList<Integer>();
     li.add(new Integer(0));
     Integer i = li.iterator().next();
Như vậy đoạn code trên đây mô tả việc tạo một đối tượng LinkedList với việc thay thế tham số E của nó bởi một kiểu cụ thể Integer và như vậy giờ đây đối tượng LinkedList li là một tập hợp chỉ có thể chứa các kiểu Integer .

2.2.4- Tại sao phải cần đến Generics? Các vấn đề (Trước phiên bản -J2SE 5.0)

Giả sử bạn muốn lưu trữ các đối tượng String trong một Vector.Do sơ xuất bạn đã  add một đối tượng kiểu Integer. Trước J2SE 1.5 bộ dịch không nhận ra điều đó. Đây chính là đoạn code không an toàn
Vector v = new Vector();
v.add(new Integer(4));
...
String s=(String)v.get(0);
Lỗi xẩy ra khi bạn ép kiểu trong quá trình chạy.
// ClassCastException
String s = (String)v.get(0);
Trước phiên bản 1.5 các tập hợp chứa các kiểu khác nhau thì đều được đánh đồng một kiểu đó chính là kiểu Object .Cụ thể là mặc dù trong ý nghĩ của bạn ,bạn muốn có một đối tượng Vector chỉ chứa các đối tượng String . Xong bộ chương trình java không hiểu nó nghĩ rằng đối tượng Vector của bạn đang chứa các đối tượng Object.Và khi bạn muốn lấy một phần tử nào đó trong Vector đó ra bạn phải ép kiểu nó về String . Và bởi vì chương trình java của bạn không biết rằng ý của bạn chỉ muốn Vector chứa các đối tượng String nên nếu chẳng may bạn add thêm một đối tượng Integer vào Vector thì bộ dịch cũng không biết nó vẫn chấp nhận vì Integer cũng vẫn là kiểu Object .File java vẫn được dịch Ok xong một đối tượng Integer không thể ép thành String ngoại lệ này xuất hiện chỉ khi chay chương trình .
Generic là một giải pháp nói cho bộ dịch biết kiểu tập hợp của tôi chỉ chứa các kiểu thế này .Nếu cố tình chứa khác đi thì việc dịch file java là không thành công.

2.2.5- Những vấn đề nào được giải quyết bởi Generics?

Những vấn đề nào được giải quyết bởi Generics? ======
  • Vấn đề của Java trước phiên bản 1.5: Các kiểu phần tử tập hợp
    • Bộ dịch không thể xác minh các kiểu dữ liệu
    • Chỉ định (Assignment) phải có ép kiểu
    • ClassCastException là một ngoại lệ có thể được ném ra trong quá trình chạy
  • Giải pháp: Generics
    • Nói cho bộ dịch biết kiểu dữ liệu của tập hợp
    • Bộ dịch sẽ biết cần phải ép kiểu thế nào
    • Ví dụ: Bộ dịch sẽ kiểm tra nếu bạn add kiểu Integer vào một tập hợp kiểu String (thời gian dịch sẽ phát ra lỗi quá trình dịch không thành công)
Với kiểu phần tử tập hợp như Vector chẳng hạn định nghĩa của Vector :Nó được generic hóa Vector<E> và class này khi được dịch nó đã mang một ý nghĩa rằng nó sẽ chỉ chứa một kiểu E mà kiểu E sẽ được thay thế cụ thể trong ứng dụng sử dụng đối tượng của class này .
Vector<String> vs = new Vector<String>();
vs.add(new Integer(5)); // Compile Error!!
vs.add(new String("hello"));
String s = vs.get(0); // No need cast

HashMap<String, Mammal> map =new HashMap<String, Mammal>();
map.put("wombat", new Mammal("wombat"));
Mammal w = map.get("wombat");

2.2.6- Generics và các kiểu con (Sub-typing)

Hãy chú ý:
  • Bạn có thể làm điều này
    • Object o = new Integer(5);
  • Và cũng có thể làm
    • Object[] or = new Integer[5];
  • Do đó bạn mong chờ có thể làm được điều dưới đây (Nhưng điều này là không thể !!!)
    • ArrayList<Object> ao = new ArrayList<Integer>();
Đây là sự khác thường so với ý nghĩ ban đầu của bạn !!!
Giả sử rằng điều này được phép, hãy xem đoạn code:
ArrayList<Integer> ai = new ArrayList<Integer>();
ArrayList<Object> ao = ai;
ao.add(new Object());
Integer i = ai.get(0);
Và đây là lời giải thích:
  1. Dòng lệnh thứ nhất bạn tạo một đối tượng List<Integer> "ai" chứa danh sách các phần tử Integer 
  2. Dòng lệnh thứ 2 bạn tạo một đối tượng List<Object> "ao" chứa các phần tử Object. "ao" được gắn bằng "ai".Như vậy thực tế là "ai" và "ao" đều trỏ vào một đối tượng trong bộ nhớ.
  3. Dòng code thứ 3 bạn thêm vào "ao" một đối tượng Object. Và thực tế nó cũng là thêm vào "ai".
  4. Dòng lệnh thứ 4 bạn lấy ra phần tử đầu tiên của "ai", nó được ép kiểu về Integer, lỗi sẽ xẩy ra tại đây.
Xem các đoạn code dưới đây làm việc ra sao ?
ArrayList<Number> an = new ArrayList<Number>();
an.add(new Integer(5)); // OK
an.add(new Long(1000L)); // OK
an.add(new String("hello")); // Error In Compile time!!

2.2.7- Wildcards là gì? Giải pháp?

  • Sử dụng kiểu thông số Wildcard <?>
  • Collection<?> có nghĩa là tập hợp(Collection) có kiểu nào đó chưa rõ ràng
  static void printCollection(Collection<?> c) {
    for (Object o : c)
     System.out.println(o);
  }
   
  public static void main(String[] args) {
    List<Integer> li = new ArrayList<Integer>(10);
     printCollection(li); // OK at Compile time
     Collection<String> cs = new Vector<String>();
     printCollection(cs); // OK at Compile time
  }
Bây giờ đối số của method printCollection(Collection<?> c) là một đối tượng tập hợp Collection mà kiểu nó chứa là một kiểu nào đó .
Giờ đây nếu bạn có đối tượng c của :Collection<String>,Collection<Integer>,...đều có thể đưa vào làm đối số của method hoàn toàn Ok!!
Có thể thay việc định nghĩa method trên bởi định nghĩa sau :
void printCollection(Collection<? extends Object> c){..}
Về cú pháp này (? extends MyType ...) sẽ được đề cập dưới đây .
Đây là một ví dụ viết một method để in ra nội dung của một tập hợp (vd Collection) của chỉ kiểu Number:
  static void printCollection(Collection<? extends Number> c) {
    for (Object o : c)
    System.out.println(o);
  }
    
  public static void main(String[] args) {
    List<Integer> li = new ArrayList<Integer>(10);
    printCollection(li);  // OK

    List<Long> ll = new ArrayList<Long>();
    printCollection(ll);  // OK

    Collection<String> cs = new Vector<String>();
    printCollection(cs); // Error at Compile time.
  }
Method printCollection(Collection<? extends Number> c) nói rằng đối số mà bạn có thể đưa vào là một đối tượng generic Collection<MyType> mà MyType là một kiểu con của Number .
Lấy ra và thêm vào một thực thể cho một tập hợp có kiểu chưa rõ ràng (Unknown type)
Bởi vì phần tử được thêm vào phải là kiểu (hoặc kiểu con) của kiểu không rõ ràng x nhưng x lại không rõ ràng với bộ dịch ,vì vậy bộ dịch không cho phép thêm vào kiểu nào ngoại trừ null
ArrayList<?> a1 = new ArrayList<Number>(10);

a1.add(new Integer(3)); // Error at Compile time
a1.add(new Long(100L)); // Error at Compile time
a1.add(new Object()); // Error at Compile time

a1.add(null);
Object o = a1.get(0);

Number n = a1.get(0);// Error at Compile time
Integer i = a1.get(0);// Error at Compile time
Với ví dụ trên tốt nhất nên khai báo cụ thể kiểu
ArrayList<Number> a1 = new ArrayList<Number>();
// thay cho khai báo
// Instead of declare
ArrayList<?> a1 = new ArrayList<Number>();

2.2.8- Định nghĩa một class Generic

  • Thêm vào các kiểu tham số vào định nghĩa của class
  • Một cách cụ thể sử dụng một chữ hoa cho kiểu
  • Sử dụng ở bất kì nơi đâu trong class nơi kiểu được đòi hỏi.
public class Pair<F, S> {
    F first;
    S second;

    public Pair(F f, S s) {
        first = f;
        second = s;
    }
}
public class Pair<F, S> {
    F first;
    S second;

    public Pair(F f, S s) {
        first = f;
        second = s;
    }

    public void setFirst(F f) {
        first = f;
    }

    public F getFirst() {
        return first;
    }

    public void setSecond(S s) {
        second = s;
    }

    public S getSecond() {
        return second;
    }
}
Cách sử dụng của các class Generic
Number n1 = new Integer(5);
String s1 = new String("Sun");
Pair<Number,String> p1 =new Pair<Number,String>(n1, s1);
 
p1.setFirst(new Long(6L));
p1.setSecond(new String("rises"));
Mở rộng (thừa kế ) các class Generic
public class PairExtended<F, S, T> extends Pair<F, S> {
    T third;

    
    PairExtended(F f, S s, T t) {
        super(f, s);
        third = t;
    }

    public T getThird() {
        return third;
    }
}
Thừa kế class Generic chỉ rõ kiểu tham số Generic
public class Pair<F, S> {
   F first;
   S second;

   public Pair(F f, S s) {
       first = f;
       second = s;
   }

   public void setFirst(F f) {
       first = f;
   }
}

// Class thừa kế class Pair chỉ rõ F
public class PairExtended1<S> extends Pair<String, S> {
   public PairExtended1(String f, S s) {
       super(f, s);
   }

   public PairExtended1(S s) {
       this("String default", s);
   }
}

// Class thừa kế class Pair chỉ rõ cả F và S
public class PairExtended2 extends Pair<String, Integer> {
   public PairExtended2(String f, Integer s) {
       super(f, s);
   }
}
- Do thừa kế các từ Pair cho nên nó cũng thừa kế các method từ Pair ví dụ method setFirst(F f){..} .Nhưng do class PairExtended thừa kế mà lại chỉ rõ F
là String rồi cho nên nó thừa kế method setFirst(F f) của Pair theo kiểu setFirst(String f) !!!

2.2.9- Kiểu thô (Raw Type)

  • Là một kiểu Generic cụ thể hóa không có kiểu tham số nào cả
  • Trước-J2SE 5.0 các class tiếp tục các chức năng của mình trên nền tảng J2SE 5.0 JVM như một kiểu thô (Raw)
// Kiểu Generic cụ thể hóa bởi kiểu tham số
List<String> ls = new LinkedList<String>();
// Kiểu Generic cụ thể hóa với không kiểu tham số nào
// tham số thô
List lraw = new LinkedList();
Mặc dù J2SE 5.0 đã viết lại toàn bộ các class kiểu tập hợp trong bó java.util bằng cách Generic hóa chúng tuy nhiên bạn vẫn có thể sử dụng class LinkedList chẳng hạn theo phong cách 1.4 như xưa .Cụ thể có thể khai báo đối tượng LinkedList myraw=new LinkedList();Đối tượng LinkedList khi không có tham số Generic được gọi là kiểu thô (raw type) .Có một ghi chú là khi sử dụng nó bạn chấp nhận sự không an toàn của các dòng code của ứng dụng.

2.2.10- Type Erasure

  • Thực tế thì thông tin về kiểu generic là không tồn tại trong suốt quá trình chạy chương trình
    • Suốt quá trình chạy, class mô tả ArrayList<String>, ArrayList<Integer> đều tương tự với mô tả của class ArrayList
    • Đưa vào kiểu generic chỉ có mục tiêu khi dịch chương trình nếu không có thông báo lỗi thì đó là một đoạn code an toàn.
// Đúng hay sai ?
// True or False?
ArrayList<Integer> ai = new ArrayList<Integer>();
ArrayList<String> as = new ArrayList<String>();
boolean b1 = (ai.getClass() == as.getClass());
System.out.println("ai.getClass().getName():"+ai.getClass().getName());
System.out.println("as.getClass().getName():"+as.getClass().getName());
System.out.println(" Do ArrayList<Integer> "
            +"and ArrayList<String> share same class? " + b1);
ai.getClass() trả về một đối tượng kiểu Class mô tả class mà đối tượng ai đang cần giữ.Khi đó ai.getClass().getName() trả về tên đầy đủ của class mô tả cho đối tượng ai bao gồm tên class và tên package.
Kết quả khi chạy dòng code trên:
      ai.getClass().getName():java.util.ArrayList
      as.getClass().getName():java.util.ArrayList
      Do ArrayList<Integer> and ArrayList<String> share same class? true
Điều đó minh họa rằng thực tế thì việc đưa generic vào chỉ có tác dụng kiểm tra trong quá trình dịch code chương trình ,sản phẩm sau khi dịch của một đoạn code không dùng generic và đoạn code có dùng generic là giống nhau . Xong đoạn code dùng generic có tính an toàn cao hơn.