Java Generic
lượt xem 34
download
Đó là điểm mới về mặt ngôn ngữ từ java 5. Mặc dù đã sử dụng Generic khá thành thạo từ lâu nhưng đến nay tôi mới có dịp đề cập về nó một cách chi tiết hơn. Một ưu điểm mà người ta thường nhắc tới chính là dùng Generic có thể hạn chế được các lỗi trong ép kiểu. Không biết nên bắt đầu từ đâu nhỉ ? Có lẽ nên bắt đầu từ Collections Framework, một trong những gói được cài đặt generic nhiều nhất ở Java 5. Container với Generic. Bạn hay dùng List, cụ thể...
Bình luận(0) Đăng nhập để gửi bình luận!
Nội dung Text: Java Generic
- Java Generic Đó là điểm mới về mặt ngôn ngữ từ java 5. Mặc dù đã sử dụng Generic khá thành thạo từ lâu nhưng đến nay tôi mới có dịp đề cập về nó một cách chi tiết hơn. Một ưu điểm mà người ta thường nhắc tới chính là dùng Generic có thể hạn chế được các lỗi trong ép kiểu. Không biết nên bắt đầu từ đâu nhỉ ? Có lẽ nên bắt đầu từ Collections Framework, một trong những gói được cài đặt generic nhiều nhất ở Java 5. Container với Generic. Bạn hay dùng List, cụ thể nhất với những người mới làm quen với java là Vector. Vector giống như một mảng động được cài đặc các functions từ List interface. Tuy nhiên, Vector có synchronized, do đó vận dụng Vector một các bừa bãi có thể làm cho code java chạy chậm hơn rất nhiều, sử dụng người anh em của nó là ArrayList và LinkedList trong những trường hợp không cần synchronized sẽ nhanh hơn. Nhưng xin được cài đặt thử vài dòng với Vector vì nó khá quen với nhiều người. Vector là một mảng động, một mảng các Object (Collections Framework trong JDK của Sun không hỗ trợ primary type). Do đó, nếu khai báo như sau thì bạn có thể chứa bất cứ kiểu dữ liệu nào được extends từ Object : Vector exam = new Vector(); Trong lập trình, chúng ta thường chứa một mảng dữ liệu cùng kiểu, chẳng hạn một Vector chứa các String hoặc integer. Ví dụ : exam.add(new Integer(1)); Do đó khi lấy lại dữ liệu chúng ta phải thực hiện việc ép kiểu, ví dụ: Integer intg = (Integer)exam.get(0); Ở những phiên bản trước, lập trình viên bắt buộc phải ép kiểu dữ liệu trả về từ một collection, điều trên thực sự là hơi bất tiện và không cần thiết với một ngôn ngữ. Hơn nữa việc ép kiểu có thể ném ra lỗi bởi dữ liệu trả về không thuộc kiểu dữ liệu mà bạn cần lấy. Chẳng hạn exam.add(“4”); và lấy lại dữ liệu Integer intg2 = (Integer)exam.get(1);. Khi đó, một lỗi runtime xảy ra. Trong lập trình, lỗi có thể xảy ra ở thời điểm compile (chủ yếu là lỗi về cú pháp) và lỗi xảy ra lúc runtime. Những lỗi xảy ra ở thời điểm chạy chương trình thường là những lỗi khó debug, NullPointerException là một ví dụ và ClassCastException cũng rất thường gặp. Trong một vài cài đặt, chúng trở thành những lỗi cứng đầu nhất với lập trình viên.
- Generic đã khắc phục được điểm yếu đó, dĩ nhiên 100% thì không thể khẳng định được nhưng việc cài đặt Generic có thể hạn chế được tối đa các lỗi ClassCastException. Chẳng hạn, bây giờ bạn có thể viết như sau: Vector exam = new Vector(); exam.add(1); Khai báo một vector dùng để chứa các giá trị kiểu Integer. Khi đó, ở bất cứ đâu nếu tôi đưa dữ liệu dưới dạng khác, chẳng hạn exam.add(“4”); thì compiler sẽ báo lỗi ngay ở lúc biên dịch chương trình và coi đó như một lỗi cú pháp. Bạn đừng ngạc nhiên khi tôi viết exam.add(1);, thực ra thì số 1 thuộc kiểu int, một primary type chứ không phải là Integer – một kiểu dữ liệu đối tượng. Tuy nhiên trong java 5 có cài đặt Boxing - Unboxing nên chúng ta có thể mặc nhiên sử dụng ở một số hoàn cảnh các giá trị kiểu int giống như là Integer và ngược lại. Nếu dữ liệu ở nhiều kiểu đối tượng khác nhau thì generic có thể khai báo ở dạng cha, tức là kiểu đối tượng chung nhất mà chúng extend. Chẳng hạn, nếu muốn tạo một vector chứa dữ liệu ở dạng string hoặc integer tôi có thể dùng Object, khai báo như sau: Vector exam = new Vector(); Nhưng khi muốn lấy lại chính xác dữ liệu thuộc kiểu String hoặc integer bạn vẫn phải dùng ép kiểu, do đó, có thể không cần khai báo generic : Vector exam = new Vector(). Khi biên dịch, bạn sẽ nhận được một thông báo về việc sự an toàn khi thực hiện các phép toán trên dữ liệu. Nếu không muốn nhận lại sự thông báo này có thể khai báo một annotation là @SuppressWarnings("unchecked"). Cụ thể một hàm main nhỏ sẽ viết như sau: @SuppressWarnings("unchecked") public static void main(String[] args) { Vector exam = new Vector(); exam.add(1); } tìm ra kiểu chung gần nhất để khai báo generic cho những loại dữ liệu có thể được lưu trữ trong collection là một cách nhằm giảm bớt lỗi runtime ngay từ lúc compile, chẳng hạn nếu muốn khai báo một vector chứa dữ liệu kiểu Integer hoặc Long tôi có thể chọn kiểu cho generic là Number bởi cả Long và Integer đều được extend từ lớp này. Cụ thể như sau : Vector exam = new Vector(); exam.add(1); Khi đó, nếu ở đâu đó, tôi có vô tình add một số dưới dạng string thì một lỗi ngữ pháp sẽ thông báo cho tôi biết ngay sau khi tôi compile class đó. Nếu phải cài đặt container, tốt nhất là nên cài đặt generic, điều này sẽ giúp container của bạn được linh hoạt và có thể tránh được các lỗi về ép kiểu dữ liệu trên các phép toán. Chẳng hạn, tôi cài một stack như sau:
- public class Stack { private Node stack = null; public T pop( ) { T result = stack.value; stack = stack.next; return result; } public boolean hasNext(){ return stack != null; } public void push(T v) { stack = new Node(v, stack); } private class Node { public T value; public Node next; Node(T v) { value = v; } Node(T v, Node n) { value = v; next = n; } } } Một stack chứa một chuỗi các node được gắn kết với nhau. Các node này chứa dữ liệu thực là một kiểu dữ liệu sẽ được khai báo khi dùng stack. Trong cài đặt Generic mặc định qui ước là (không nhất thiết phải đặt là T, bạn có thể đặt A, B, C, E,... bất kì). Nó tượng trưng cho dữ liệu thực được đồng nhất ở các phép toán. khai báo trên Stack đồng nhất với Node, trong node chứa giá trị thực của một đối tượng thuộc T nào đó mà ta chưa biết: value. Các phép toán trên stack cũng đồng nhất các kiểu dữ liệu truyền vào và lấy ra là T. Thực chất T là một tượng trưng sẽ được cụ thể hóa khi sử dụng. Khi đó, tôi có thể dùng stack này để chứa nhiều kiểu dữ liệu khác nhau với sự linh hoạt và an toàn khi khai báo generic. Ví dụ: Stack stackInt = new Stack(); stackInt.push(4); stackInt.push(new Integer(3)); while(stackInt.hasNext()){ System.out.println("integer value :"+stackInt.pop()); } Stack stackStr = new Stack(); stackStr.push("4"); stackStr.push(new Integer(3).toString());
- while(stackStr.hasNext()){ System.out.println("string value :"+stackStr.pop()); } Trong các class của Collection framework cũng được cài đặt generic tương tự, chẳng hạn kiểu generic của lớp ArrayList sẽ được nhận vào từ class viết như sau: public class ArrayList implements List{ T [] values ; ... public ArrayList(int initialCapacity) { super(); values = (T[])new Object[initialCapacity]; } ... } Khi tạo mới mảng dữ liệu chúng ta không thể trực tiếp viết: T[] values = new T[initialCapacity]; mà phải tạo một mảng object rồi ép kiểu chúng lại thành mảng T – mảng có kiểu giá trị thực sự sẽ được khai báo khi sử dụng. Việc ép kiểu trong compiler của IBM có thể viết như sau T[] values = T[].class.cast(new Object[initialCapacity]); cách này có vẻ chuyên nghiệp hơn nhưng khi compile bằng javac của Sun thì gặp phải lỗi cú pháp “cannot select from a type variable” mặc dù trong runtime thì JVM có thể chạy được. Khi đó, có thể ép theo kiểu T[] values = (T[])new Object[initialCapacity];. Vấn đề về không được phép new T[initialCapacity] hay new T(); là do kiểu dữ liệu thực chưa được biết chính xác chúng có những contructor nào (bao nhiêu và kiểu dữ liệu của thông số truyền vào khi tạo object).Do đó chúng ta không thể new trực tiếp được các generic type. Tuy nhiên, nếu đã có class thì có thể tạo object được, bởi lẽ khi đó chúng ta có thể dùng reflection lấy ra được contructor của class truyền vào. Method sau là một ví dụ : public T createInstance(Class clazz) throws Exception { return clazz.newInstance(); } việc new Object[initialCapacity] thì không liên gì đến contructor của generic type (của T), chúng chỉ việc cấp pháp vùng nhớ lưu địa chỉ con trỏ trỏ đến một mảng object nào đó mà T cũng là một dạng object. Do đó, khởi tạo một mảng địa chỉ, sau đó ép mảng địa chỉ này trỏ về dạng T là phép toán hợp lệ. Trong method tạo instance trên, class của kiểu dữ liệu thực sẽ phải tồn tại một contructor rỗng với quyền truy cập là public. Nếu không chúng ta phải dùng reflection, mặc nhiên generic type bắt buộc phải tồn tại một contructor nào đó đã biết trước số lượng và kiểu dữ liệu của các thông số truyền vào. Nếu không lỗi về IllegalArgumentException, NoSuchMethodException hoặc SecurityException có thể xảy ra. Ở phần này, xin được trình bày về các thức cài đặt generic trong code cụ thể. Ai đã từng làm việc với C++ đều có thể kết luận rằng generic trong java cũng có điểm tương đồng như template trong C++, nhưng ở java thì generic có sự đơn giản và phức tạp xét trên
- một vài tiêu chí nào đó. Generic giống như một khai báo biến tượng trưng. Điều đó đồng nghĩa với việc có generic toàn cục và có generic cục bộ. Đối với class, interface hay các abstract đều có thể cài đặt generic, chúng được bao bọc bởi cặp đóng - mở của ngoặc nhọn, biểu thị bằng một hoặc nhiều chữ cái và có thể khai báo nhiều generic trong class đó. Class sau đây minh họa việc khai báo generic. public class GenericExam { T t; EN english; public GenericExam(T t, EN en){ this.t = t; this.english = en; System.out.println("init "+hashCode()+" ... "+t+" ... "+english); } } Như chúng ta đã thấy, generic cũng tuân thủ các quy tắc đặt tên thông thường trong Java. Tuy nhiên, trong cài đặt, việc khai báo generic thường được dùng bằng một chữ cái hoa cho dễ nhận biết. Và bằng kinh nghiệm bản thân, tôi cũng xin khuyến cáo thiết kế một class không nên vượt quá 3 khai báo generic, mặc dù chưa có những khẳng định chính xác về những khuyết điểm của việc cài đặt quá nhiều generic, nhưng một điều chắc chắn rằng, nếu dùng quá nhiều generic sẽ gây nên tình trạng rối code cho bản thân cũng như những người sử dụng các class của bạn. Ở những cuốn sách hay các tài liệu liên quan đến gerneric, người ta thường tập trung chủ yếu vào việc minh họa và khai thác các ý tưởng thiết kế ở collection. Generic đã phản ánh tối đa tác dụng của nó trên các dạng framework đó. Tuy nhiên, mở rộng vấn đề thì generic có thể cài đặt ở bất cứ đâu phụ thuộc vào ý tưởng thiết kế một hay nhiều class của bạn. Nếu như tác dụng đầu tiên của generic là tránh được các lỗi ép kiểu thì qua kinh nghiệm code với eXo Portal 2.0 (tên mã là WebOS) đã cho tôi thấy, generic còn tạo ra sự linh hoạt, gọn nhẹ, tường minh,... mặc dù chúng thực chất là những trừu tượng hóa dữ liệu. Xét ở ví dụ trên, tôi khai báo tới bảy generic khác nhau, class chứa hai biến toàn cục thuộc dạng T và EN. Chú ý: các biến static sẽ không được khai báo kiểu là generic. Khi tạo mới Object của GenericExam, coder có thể phải cung cấp đủ 7 khai báo cho đối tượng được tạo ra từ class đó nếu khai báo generic. Đoạn code sau là một ví dụ minh họa : GenericExam exp; exp = new GenericExam("yahoo", new Double(3)); System.out.println(exp); GenericExam exp2 = new GenericExam("google", 8); System.out.println(exp2); với exp, khi tạo object, chúng ta có đầy đủ 7 kiểu dữ liệu, contructor nhận vào hai đối tượng thuộc kiểu thứ nhất và kiểu thứ tư là String và Double. Nếu bạn truyền vào một đối số khác, chắc chắn bạn sẽ không thể biên dịch nổi. Ở exp2, tôi không khai báo generic, về compile và run, ngoài một cảnh báo kiểu dữ liệu truyền vào thì instance exp2 vẫn được tạo ra một cách bình thường. Tuy nhiên, chúng ta có thể lấy thêm một ví dụ nữa để xem xét việc không khai
- báo generic, dữ liệu sẽ tồn tại như thế nào trong object? Tôi bổ sung thêm một hàm (setter) cho GenericExam như sau : public void setT(T t){ this.t = t; System.out.println("set data "+hashCode()+" ... "+t+" ... "+english); } và đoạn code sử dụng generic sau : GenericExam exp2 = new GenericExam("google", 8); exp2.setT(45); System.out.println(exp2); Khi compile và chạy, mọi việc đều diễn ra hết sức bình thường, ở init, đối tượng t thuộc kiểu String nhưng sau khi setT với đầu vào là một số nguyên thì t lại thuộc kiểu Integer. Điều đó cho phép chúng ta kết luận rằng : mặc định, nếu không khai báo thì mọi generic sẽ là object, khi đó chúng ta mặc nhiên có thể đặt giá trị vào biến với các dữ liệu thuộc nhiều kiểu object khác nhau. Xin đề cập vấn đề này sâu hơn ở phần 3 của bài viết với reflection. Nếu như bạn khai báo generic, dĩ nhiên khi exp.setT(45) thì sẽ không thể compile được bởi kiểu dữ liệu nhận vào của setter đó phải là String. Việc không khai báo generic thường không ảnh hưởng nhiều đến những cài đặt trừ phép toán ép kiểu, dĩ nhiên việc không khai báo generic sẽ làm nảy sinh sự trường hợp lập trình viên đưa vào dữ liệu sai kiểu, và khi JVM thực hiện một phép ép kiểu, chúng sẽ throw ra một exception trong runtime. Đây là có thể là lỗi nguy hiểm ở nhiều chương trình. Đối với interface hoặc abstract, inner class, outer class, việc cài đặt generic cũng diễn ra tương tự như trên, chúng ta có những khai báo toàn cục theo ngữ pháp đặt tên, nằm trong cặp ngoặc nhọn đóng mở và kế cận ở tên class hoặc interface. Ví dụ sau là một minh họa cho việc khai báo generic ở interface. public interface Listener { public void execute(T t); } và một cài đặt từ Listener : public class SelectionListener implements Listener { public void execute(String t){ System.out.println(t); } } Như vậy, kiểu String sẽ là kiểu dữ liệu được dùng xuyên suốt các cài đặt của Listener trong SelectionListener. Một thay đổi khác có thể là lỗi ngữ pháp, không cho phép biên dịch class. Nếu không khai báo generic thì mặc nhiên kiểu của generic sẽ là object. Ví dụ :
- public class SelectionListener implements Listener { public void execute(Object t){ System.out.println(t); } } Trong trường hợp đó generic sẽ là Object, không chấp nhận các primary type (dữ liệu nguyên thủy) như int, double, long, char, byte,... Nếu tôi nhớ không nhầm thì hình như Apache cũng có cài đặt một Collection Framework hỗ trợ các primary type và có sử dụng generic, về cách thức cài đặt cụ thể như thế nào thì tôi chưa có dòm qua và tôi đang viết bài viết này ở nhà, nơi không có internet. Việc extends hay implements một class hoặc interface có cài đặt generic cũng có tính chất bắc cầu. Trở lại với SelectionListener, chúng ta có một implements sau : public class SelectionListener implements Listener { public void execute(E e){ System.out.println(e); } } Generic được khai báo cho SelectionListener và chúng thông qua cho Listener interface, khi tạo object chúng ta có thể viết : SelectionListener listener = new SelectionListener(); listener.execute(23); Cũng có thể không cần pass generic của SelectionListener qua cho Interface,như đoạn code sau : public class SelectionListener implements Listener { public void execute(String e){ System.out.println(e); } } ... SelectionListener listener = new SelectionListener(); listener.execute("string value"); Trong ví dụ trên, generic của SelectionListener không liên quan gì đến Listener mà generic của nó ở đây nó đã được khai báo là String. Tôi thử cài đặt generic cho annotation và enum nhưng đều thất bại. Annotation là một dạng meta data (dữ liệu đặc tả chứ không nên dịch là siêu dữ liệu như một số cuốn sách tin học của Việt nam) thì chắc chắn không cài được generic là đúng rồi. Và enum cũng vậy, chúng là loại dữ liệu bất biến trong một JVM. Bây giờ chúng ta đề cập đến như khai báo cục bộ của generic, một method được viết như
- sau. public void setData(M m) { System.out.println("print "+m + " instanceOf "+m.getClass()); } và code sử dụng : exp.setData("nhuthuan.blogspot.com"); exp.setData(3.02); Kết quả in ra sẽ là: print nhuthuan.blogspot.com instanceOf class java.lang.String print 3.02 instanceOf class java.lang.Double Kiểu generic M được dùng cho method setData, chúng tuân thủ quy tắc đặt tên và cú pháp, generic được khai báo trước kiểu trả về của method bởi lẽ đôi khi chúng ta lấy kết quả trả về cũng là một đối tượng thuộc kiểu dữ liệu của generic đã khai báo. Ví dụ sau là một minh họa : public R createInstance(Class clazz) throws Exception { return clazz.newInstance(); } Và code sử dụng : try { StringBuilder builder ; builder = exp.createInstance(StringBuilder.class); builder.append("vietspider"); System.out.println("print "+builder + " instanceOf "+builder.getClass()); } catch(Exception err) { err.printStackTrace(); } Kết quả in ra là : init 17523401 ... class java.lang.String ... 3.0 print vietspider instanceOf class java.lang.StringBuilder Với các static method, cài đặt trên là tương tự. Constructor cũng là một method đặc biệt, do đó, chúng có cú pháp cài đặt generic như cài method. Chúng ta xét một ví dụ sau : public class GenericExam { T t; public GenericExam(G g){ t = (T)g;
- System.out.println("value t "+t+" ... "+t.getClass()); } public T getT() { return t; } } Có tới 3 kiểu generic, hai được khai báo trên class trong đó biến t là biến toàn cục sử dụng generic thứ nhất làm kiểu dữ liệu. Khi tạo instance thì G được truyền vào, nó sẽ được ép kiểu trở về kiểu của T. Liệu code trên sẽ có thể chạy đúng như ta đã suy nghĩ hay không? Xem xét cài đặt sau : GenericExam exp4; exp4 = new GenericExam(new Float(2.3)); int a = exp4.getT(); T lúc này là Integer và G là Float. Nếu theo logic suy luận của tôi thì t có giá trị là 2. Trước tiên, chúng ta hãy xem generic được truyền vào contructor, chúng khai báo giữa toán tử new và tên Class new GenericExam... tiếp sau đó mới là generic của class. Truyền vào một object đúng kiểu của khai báo trong constructor là new Float(2.3). Khi chạy code cài đặt trên, chúng ta sẽ ra kết quả sau : value t 2.3 ... class java.lang.Float Exception in thread "main" java.lang.ClassCastException: java.lang.Float at GenericExam.main(GenericExam.java:72) Ngạc nhiên chưa, giá trị của t vẫn là Float, đúng kiểu của constructor, như vậy phép toán t = (T)g; đã không thực hiện đúng yêu cầu và cũng không throw ra lỗi. Tuy nhiên, nếu ta thực hiện get là exp4.getT() thì ClassCastException sẽ được ném ra. Điều này cho thấy phép ép kiểu t = (T)g đôi khi không thực hiện đúng yêu cầu và những giao tiếp ra bên ngoài mới thực sự ép kiểu generic, lúc đó, lỗi có thể được throw ra. Static field thì chúng ta không dùng được generic nhưng ở static method, chúng ta hoàn toàn có thể cài đặt generic và sử dụng bình thường. Mẩu code sau từ một bài trả lời của tôi trên JVN là một ví dụ: class abstract CommonSetting { public static Class ResourcesClass = ResourceAdapter.class; } public class Resources{ public static String getImagePath( String fileName) { System.out.println(T.ResourcesClass.getName()); return T.ResourcesClass.getResource("").getPath() ;
- } } và khi sử dụng : System.out.println(Resources.getImagePath("name")); Setting set = new Setting(); System.out.println(Resources.getImagePath("name")); Với static code, đặt ngoài method và trong class thì hiện tại tôi chưa có cách nào để khai báo generic. Điều này có thể khắc phục bằng cách khai báo trên class, điều đó có nghĩa là một dạng vay mượn từ khai báo toàn cục, tuy nhiên chúng có thể chỉ được dùng trong một đoạn static code nào đó mà thôi. Với lập trình, kế thừa không còn là khái niệm xa lạ. Generic cũng vậy, là một trừu tượng dữ liệu, bản thân chúng cũng có thể kế thừa hoặc được kế thừa từ một kiểu dữ liệu khác. Trở lại ví dụ bài viết đầu tiên, chúng ta có một Vector và kiểu khai báo là Number, chúng được dùng để lưu trữ tất cả các đối tượng được extends từ Number. Xem xét một ví dụ sau : public List toList(E...e) { Vector values = new Vector(); for(E ele : e){ values.add(ele); } return values; } Hàm toList nhận đầu vào là một mảng thuộc kiểu E, kiểu E được định nghĩa với khai báo , E là một kiểu extends từ Number. Hàm toList sẽ tạo ra một Vector và lưu trữ các phần tử từ mảng đầu vào, Vector values là giá trị được trả về. Trong các hoàn cảnh cài đặt được đề cập ở bài viết thứ hai, chúng ta có thể hoàn toàn định nghĩa một generic được thừa kế từ một kiểu dữ liệu nào đó. Theo tiếng Anh thì chúng được gọi là subtyping, tạm dịch là kiểu dữ liệu con. WildCards Dịch là gì nhỉ ? WildCard - ký tự đại diện. Vâng, chúng sẽ được ký hiệu là dấu hỏi. Wildcard có thể đứng một mình trong cài đặt, cũng có thể là khai báo theo dạng kế thừa hoặc được kết thừa. Lần lượt xét đến từng ví dụ sau. Wildcard trong khai báo generic : List list. Wildcard trong trường hợp này như một khai báo generic chưa biết chắc, mặc định chúng ta tạm hiểu ngầm là khai báo list chứa các object. Kiểu generic sẽ được tường minh khi khởi tạo list, chẳng hạn như ví dụ sau: List list = new ArrayList(); //... list = new ArrayList(); //...
- list = new Vector(); Như chúng ta thấy, đầu tiên là list được gán cho một đối tượng ArrayList với kiểu generic là Number, lúc sau list được gán là một đối tượng ArrayList với kiểu generic là Integer, cuối cùng là vector với kiểu String. Wildcard như một “lối thoát” cho sự linh hoạt của đối tượng khai báo generic, nghĩa là chúng sẽ tường minh khi được gán đối tượng. Chúng ta không thể tạo một đối tượng với khai báo generic dạng wildcard, chẳng hạn khai báo sau sẽ gặp lỗi ngữ pháp. List list = new ArrayList(); Trong cài đặt wildcard thường được dùng để ép kiểu, wildcard cũng có thể extends hoặc khai báo được với kiểu được thừa kế. Các mẩu code sau sẽ là minh họa. List
- Tuy nhiên với remove thì hoàn toàn có thể : arrayList.remove(4); là code hợp lệ. Với các khai báo extends cho wildcard thì không thể đặt dữ liệu nhưng với những khai báo super thì lại hoàn toàn có thể: ArrayList
- Mặc dù wildcard là một cách thức trợ giúp cho sự linh động trong khai báo generic nhưng những hạn chế trên đã khiến cho nhiều cài đặt đắn đo trước khi quyết định mô hình của wildcard. Nói tóm lại, chúng rất ít khi được lập trình viên sử dụng trong những thiết kế của mình, ít nhất là với tầm hiểu biết của tôi. Generic của Generic. Đúng vậy, nếu chúng ta coi việc thể định nghĩa nhiều generic cho một class hoặc method có thể tạo thành độ rộng thì việc dùng generic cho generic giống như độ sâu trong sử dụng. Tuy nhiên, generic là một dạng trừu tượng dữ liệu, do đó trước khi chúng một định danh cụ thể được cung cấp thì chúng ta không thể trừu tượng cái trừu tượng, đó là nguyên nhân gây ra lỗi compile của cài đặt sau : public class Generic2 { public Generic2(){ } } Nhưng, nhưng đã là định danh cụ thể thì điều đó đồng nghĩa với việc chúng ta có thể dùng generic, những dòng code dưới đây là minh chứng cụ thể : List list ; list = new ArrayList(); khai báo và khởi tạo trên là hợp lệ bởi chúng là các định danh cụ thể, tuy nhiên level của generic quá sâu và cũng chẳng ai dại gì mà tự làm khó mình bằng cài đặt như vậy. Generic với reflection. Reflection hiện nay không còn xa lạ gì với nhiều người bởi lẽ vì sự linh động trong design cũng như sự lười biếng của lập trình viên mà nó được cài đặt quá nhiều. Reflection có thể ví von như một dạng ánh xạ hay ảnh. Từ java 5 trở đi, ngoài collection framework thì bản thân tôi cho rằng reflection framework là ứng cử viên thứ hai cho tần xuất cài đặt generic. Xét về mật độ chúng ta có thể thấy khái nhiều các định nghĩa trong Reflection cài đặt generic: class, constructor, method, field,... và đoạn code sau mà một minh họa cho việc sử dụng generic với reflection. Class clazz = String.class; Constructor constructor = clazz.getConstructor(new Class[] {String.class}); String strObj = constructor.newInstance("yahoo hee"); System.out.println(strObj +" is instanceOf "+strObj.getClass()); Strong một vài thiết kế, nếu như chúng ta không thể gọi toán tử new thì việc kết hợp reflection với generic có thể tạo ra object khá hiệu quả. Code sau là một minh họa:
- public static T createObjects(Class clazz, Class [] parameterTypes, Object...objs) throws Exception { Constructor constructor = clazz.getConstructor(parameterTypes); constructor.setAccessible(true); return constructor.newInstance(objs); } và code sử dụng : String str = Generic2.createObjects(String.class, new Class[]{String.class}, "hi"); System.out.println(str); int number = Generic2.createObjects(Integer.class, new Class[]{String.class}, "2"); System.out.println(number); Những mô hình như trên có thể giả lập các cài đặt closure thông qua reflection, một điểm mới sẽ được giới thiệu ở bản JDK 7.0. Tuy nhiên, reflection là một dạng ánh xạ, do đó, chúng chạy khá chậm mặc dù có thể giảm code cài đặt cho lập trình viên cũng như sự linh hoạt. Tôi đã gặp những bài học đắt giá với việc cài đặt reflection và cuối cùng thì rút ra được kinh nghiệm cho bản thân là cân nhắc giữa hiệu quả mang lại giữa một design đơn thuần với việc lựa chọn reflection. Đây cũng là nguyên nhân khiến hibernate càng ngày càng trở nên nặng nề, chậm chạp với mô hình object data mapping của nó. Với JSR 170, eXo đã quyết định loại bỏ dần hibernate ra khỏi những module trong flatform và portal. Điều mà chúng tôi cần mang lại là một kiến trúc khỏe và linh hoạt hơn là những thiết kế phức tạp nặng nề đến mức không cần thiết khi phải căn ke những khả năng của lập trình viên. Có lẽ xin được dừng việc đề cập reflection với generic tại đây bởi lẽ cũng không cần thiết đến mức phải trình bày lan man. Bài toán nhỏ: Trong một danh sách các phần tử thuộc nhiều loại khác nhau, không ép kiểu vậy làm sao có thể lấy ra được danh sách con các phần tử thuộc một loại cụ thể? Genergics đề xuất như một giải pháp an toàn, sạch sẽ, gọn hơn,... trong viết code, nghĩa là ép kiểu, một phép toán phổ biến trong lập trình Java có thể sẽ san sẻ bớt nhiệm vụ của nó cho Generics. Khi nào thấy cần ép kiểu, hãy nghĩ đến xem có cài đặt Generics được hay không ? Giả sử có một lớp Child là một abstract và hai dẫn xuất là SimpleChild và ComplexChild, một Children là một collection chứa các object tạo bởi Child nằm trong hai loại ComplexChild và SimpleChild, cài đặt sau cho phép lấy ra danh sách các phần tử là instance của ComplexChild hoặc SimpleChild. /** * Author : Nhu Dinh Thuan * nhudinhthuan@yahoo.com * Jun 21, 2006 */
- abstract public class Child { private String name = "Child"; public Child(String n){ name = n; } public String getName(){ return name; } public void setName(String n) { name = n; } } /** * Author : Nhu Dinh Thuan * nhudinhthuan@yahoo.com * Jun 21, 2006 */ public class SimpleChild extends Child { public SimpleChild(String name){ super("SinpleChild : "+name); } } /** * Author : Nhu Dinh Thuan * nhudinhthuan@yahoo.com * Jun 21, 2006 */ public class ComplexChild extends Child { public ComplexChild(String name){ super("ComplexChild : "+name); } } import java.util.ArrayList; import java.util.List; /** * Author : Nhu Dinh Thuan * nhudinhthuan@yahoo.com * Jun 21, 2006 */ public class Children { private List children; public Children(){
- children = new ArrayList(); } public void add(T obj){ children.add(obj); } public List getChildOfType(Class clazz){ List list = new ArrayList(); for(Child child : children){ if(clazz.isInstance(child)) list.add(clazz.cast(child)); } return list; } } import java.util.List; /** * Author : Nhu Dinh Thuan * nhudinhthuan@yahoo.com * Jun 21, 2006 */ public class Test { public static void main(String[] args){ Children children = new Children(); children.add(new SimpleChild(" child 1")); children.add(new ComplexChild(" child 2")); children.add(new ComplexChild(" child 3")); children.add(new SimpleChild(" child 4")); children.add(new ComplexChild(" child 5")); children.add(new SimpleChild(" child 6")); children.add(new SimpleChild(" child 7")); children.add(new SimpleChild(" child 8")); children.add(new ComplexChild(" child 9")); children.add(new ComplexChild(" child 10")); List list = children.getChildOfType(SimpleChild.class); for(SimpleChild child : list) System.out.println(child.getName()); } } Khi chạy Test, kết quả sẽ in ra như sau : SinpleChild : child 1 SinpleChild : child 4 SinpleChild : child 6 SinpleChild : child 7 SinpleChild : child 8
CÓ THỂ BẠN MUỐN DOWNLOAD
-
Giáo trình Lập trình viên công nghệ Java (Module 2) - Trung tâm tin học ĐH KHTN
129 p | 226 | 80
-
Bài giảng Cấu trúc dữ liệu: Chương 2 - Nguyễn Xuân Vinh
38 p | 113 | 18
-
Lập mô hình với Java: Một cuốn sách bài tập về UML, Phần 2 Logic điều kiện trong các sơ đồ tuần tự
9 p | 173 | 18
-
Bài giảng Lập trình nâng cao với Java
170 p | 99 | 14
-
LTHDT - Bài 07. Đa hình (Polymophism)
49 p | 92 | 14
-
Java Message Service
0 p | 126 | 10
-
Generics the Java Programming Language
24 p | 100 | 8
-
Bài giảng Lập trình hướng đối tượng: Phần 2
113 p | 33 | 8
-
Professional PHP Programming phần 10
84 p | 68 | 6
-
A Guide To Advanced Java - Generics
2 p | 91 | 6
-
Bài giảng Lập trình Java 2 - Bài 6: Genegic
30 p | 44 | 5
-
Bài giảng Lập trình hướng đối tượng: Chương 0 - Châu Thị Bảo Hà
6 p | 97 | 3
-
Bài thực hành Lập trình Java 2 - Bài Assignment
5 p | 100 | 1
Chịu trách nhiệm nội dung:
Nguyễn Công Hà - Giám đốc Công ty TNHH TÀI LIỆU TRỰC TUYẾN VI NA
LIÊN HỆ
Địa chỉ: P402, 54A Nơ Trang Long, Phường 14, Q.Bình Thạnh, TP.HCM
Hotline: 093 303 0098
Email: support@tailieu.vn