Thinking in java——Generics Applying a method to a sequence
来源:互联网 发布:数据库实验报告 编辑:程序博客网 时间:2024/05/11 00:20
Reflection provides some interesting possibilities, but it relegates all the type checking to run time, and is thus undesirable in many situations. If you can achieve compile-time type checking, that's usually more desirable. But is it possible to have compile-time type checking and latent typing?
Let's look at an example that explores the problem. Suppose you want to create an apply( ) method that will apply any method to every object in a sequence. This is a situation where interfaces don't seem to fit. You want to apply any method to a collection of objects, and interfaces constrain you too much to describe "any method." How do you do this in Java?
Initially, we can solve the problem with reflection, which turns out to be fairly elegant because of Java SE5 varargs:
//: generics/Apply.java// {main: ApplyTest}import static net.mindview.util.Print.print;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;public class Apply {public static <T, S extends Iterable<? extends T>> void apply(S seq,Method f, Object... args) {try {for (T t : seq)f.invoke(t, args);} catch (Exception e) {// Failures are programmer errorsthrow new RuntimeException(e);}}}class Shape {public void rotate() {print(this + " rotate");}public void resize(int newSize) {print(this + " resize " + newSize);}}class Square extends Shape {}class FilledList<T> extends ArrayList<T> {public FilledList(Class<? extends T> type, int size) {try {for (int i = 0; i < size; i++)// Assumes default constructor:add(type.newInstance());} catch (Exception e) {throw new RuntimeException(e);}}}class ApplyTest {public static void main(String[] args) throws Exception {List<Shape> shapes = new ArrayList<Shape>();for (int i = 0; i < 10; i++)shapes.add(new Shape());Apply.apply(shapes, Shape.class.getMethod("rotate"));Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 5);List<Square> squares = new ArrayList<Square>();for (int i = 0; i < 10; i++)squares.add(new Square());Apply.apply(squares, Shape.class.getMethod("rotate"));Apply.apply(squares, Shape.class.getMethod("resize", int.class), 5);Apply.apply(new FilledList<Shape>(Shape.class, 10),Shape.class.getMethod("rotate"));Apply.apply(new FilledList<Shape>(Square.class, 10),Shape.class.getMethod("rotate"));SimpleQueue<Shape> shapeQ = new SimpleQueue<Shape>();for (int i = 0; i < 5; i++) {shapeQ.add(new Shape());shapeQ.add(new Square());}Apply.apply(shapeQ, Shape.class.getMethod("rotate"));}} /* (Execute to see output) */// :~
In Apply, we get lucky because there happens to be an Iterable interface built into Java which is used by the Java containers library. Because of this,the apply( ) method can accept anything that implements the Iterable interface, which includes all the Collection classes such as List. But it can also accept anything else, as long as you make it Iterable—for example, the SimpleQueue class defined here and used above in main( ):
//: generics/SimpleQueue.java// A different kind of container that is Iterableimport java.util.*;public class SimpleQueue<T> implements Iterable<T> { private LinkedList<T> storage = new LinkedList<T>(); public void add(T t) { storage.offer(t); } public T get() { return storage.poll(); } public Iterator<T> iterator() { return storage.iterator(); }} ///:~
In Apply.java, exceptions are converted to RuntimeExceptions because there's not much of a way to recover from exceptions—they really do represent programmer errors in this case.
Note that I had to put in bounds and wildcards in order for Apply and FilledList to be used in all desired situations. You can experiment by taking these out, and you'll discover that some applications of Apply and FilledList will not work.
FilledList presents a bit of a quandary. In order for a type to be used, it must have a default (no-arg) constructor. Java has no way to assert such a thing at compile time, so it becomes a runtime issue. A common suggestion to ensure compile-time checking is to define a factory interface that has a method that generates objects; then FilledList would accept that interface rather than the "raw factory" of the type token. The problem with this is that all the classes you use in FilledList must then implement your factory interface.Alas, most classes are created without knowledge of your interface, and therefore do not implement it. Later, I'll show one solution using adapters.
But the approach shown, of using a type token, is perhaps a reasonable trade-off (at least as a first-cut solution). With this approach, using something like FilledList is just easy enough that it may be used rather than ignored. Of course, because errors are reported at run time, you need confidence that these errors will appear early in the development process.
Note that the type token technique is recommended in the Java literature,such as Gilad Bracha's paper Generics in the Java Programming Language,where he notes, "It's an idiom that's used extensively in the new APIs for manipulating annotations, for example." However, I've discovered some inconsistency in people's comfort level with this technique; some people strongly prefer the factory approach, which was presented earlier in this chapter.
Also, as elegant as the Java solution turns out to be, we must observe that the use of reflection (although it has been improved significantly in recent versions of Java) may be slower than a non-reflection implementation, since so much is happening at run time. This should not stop you from using the solution, at least as a first cut (lest you fall sway to premature optimization),but it's certainly a distinction between the two approaches.
When you don't happen to have the right interface
For example, let's generalize the idea in FilledList and create a parameterized fill( ) method that will take a sequence and fill it using a Generator. When we try to write this in Java, we run into a problem,because there is no convenient "Addable" interface as there was an Iterabl interface in the previous example. So instead of saying,"anything that you can call add( ) for," you must say, "subtype of Collection." The resulting code is not particularly generic, since it must be constrained to work with Collection implementations. If I try to use a class that doesn't implement Collection, my generic code won't work. Here's what it looks like:
//: generics/Fill.java// Generalizing the FilledList idea// {main: FillTest}import java.util.*;// Doesn't work with "anything that has an add()." There is// no "Addable" interface so we are narrowed to using a// Collection. We cannot generalize using generics in// this case.public class Fill { public static <T> void fill(Collection<T> collection, Class<? extends T> classToken, int size) { for(int i = 0; i < size; i++) // Assumes default constructor: try { collection.add(classToken.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } }}class Contract { private static long counter = 0; private final long id = counter++; public String toString() { return getClass().getName() + " " + id; }}class TitleTransfer extends Contract {}class FillTest { public static void main(String[] args) { List<Contract> contracts = new ArrayList<Contract>(); Fill.fill(contracts, Contract.class, 3); Fill.fill(contracts, TitleTransfer.class, 2); for(Contract c: contracts) System.out.println(c); SimpleQueue<Contract> contractQueue = new SimpleQueue<Contract>(); // Won't work. fill() is not generic enough: // Fill.fill(contractQueue, Contract.class, 3); }} /* Output:Contract 0Contract 1Contract 2TitleTransfer 3TitleTransfer 4*///:~
This is where a parameterized type mechanism with latent typing is valuable,because you are not at the mercy of the past design decisions of any particular library creator, so you do not have to rewrite your code every time you encounter a new library that didn't take your situation into account (thus the code is truly "generic"). In the above case, because the Java designers (understandably) did not see the need for an "Addable" interface, we are constrained within the Collection hierarchy, and SimpleQueue, even though it has an add( ) method, will not work. Because it is thus constrained to working with Collection, the code is not particularly "generic." With latent typing, this would not be the case.
Simulating latent typing with adapters
//: generics/Fill2.java// Using adapters to simulate latent typing.// {main: Fill2Test}import generics.coffee.*;import java.util.*;import net.mindview.util.*;import static net.mindview.util.Print.*;interface Addable<T> { void add(T t); }public class Fill2 { // Classtoken version: public static <T> void fill(Addable<T> addable, Class<? extends T> classToken, int size) { for(int i = 0; i < size; i++) try { addable.add(classToken.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } // Generator version: public static <T> void fill(Addable<T> addable, Generator<T> generator, int size) { for(int i = 0; i < size; i++) addable.add(generator.next()); }}// To adapt a base type, you must use composition.// Make any Collection Addable using composition:class AddableCollectionAdapter<T> implements Addable<T> { private Collection<T> c; public AddableCollectionAdapter(Collection<T> c) { this.c = c; } public void add(T item) { c.add(item); }}// A Helper to capture the type automatically:class Adapter { public static <T> Addable<T> collectionAdapter(Collection<T> c) { return new AddableCollectionAdapter<T>(c); }}// To adapt a specific type, you can use inheritance.// Make a SimpleQueue Addable using inheritance:class AddableSimpleQueue<T>extends SimpleQueue<T> implements Addable<T> { public void add(T item) { super.add(item); }}class Fill2Test { public static void main(String[] args) { // Adapt a Collection: List<Coffee> carrier = new ArrayList<Coffee>(); Fill2.fill( new AddableCollectionAdapter<Coffee>(carrier), Coffee.class, 3); // Helper method captures the type: Fill2.fill(Adapter.collectionAdapter(carrier), Latte.class, 2); for(Coffee c: carrier) print(c); print("----------------------"); // Use an adapted class: AddableSimpleQueue<Coffee> coffeeQueue = new AddableSimpleQueue<Coffee>(); Fill2.fill(coffeeQueue, Mocha.class, 4); Fill2.fill(coffeeQueue, Latte.class, 1); for(Coffee c: coffeeQueue) print(c); }} /* Output:Coffee 0Coffee 1Coffee 2Latte 3Latte 4----------------------Mocha 5Mocha 6Mocha 7Mocha 8Latte 9*///:~
Fill2 doesn't require a Collection as Fill did. Instead, it only needs something that implements Addable, and Addable has been written just for Fill—it is a manifestation of the latent type that I wanted the compiler to make for me.
- Thinking in java——Generics Applying a method to a sequence
- thinking in java——Generics
- How to add a DELAY in applying transactions in GoldenGate?
- When to use static method in a java class
- Thinking in java-38 Java 泛型Generics in java
- How to: Protect Against Script Exploits in a Web Application by Applying HTML Encoding to Strings
- A Generic method to modify the names in a JSONObject
- How to Create a JAVA Native Method
- 学习总结-Thinking In Java Chapter 15 generics
- Thinking in C++ 读书笔记(A)
- thinking in java 附录A 使用非JAVA代码
- How to solve "java.lang.VerifyError: Expecting a stackmap frame at branch target 6 in method"
- Thinking in Java 读书笔记 —— 1.Introducation to Object
- java 示例2——Get the class name in a static method
- java:Cannot refer to a non-final variable tx inside an inner class defined in a different method
- Thinking In Java 之 How a garbage collector works
- a method to set the title of webpart in code
- Mocking a method to throw an exception in C#
- Windows学习心得【静态类】
- Android彻底退出应用程序
- 浅谈linux字符设备注册
- linux中断线程化分析
- 标准C++中的string类的用法总结
- Thinking in java——Generics Applying a method to a sequence
- pat 1017
- 解決EXP-00091
- 我宁可再来一遍。
- 解析android中隐藏与显示软键盘及不自动弹出键盘的实现方法
- Introdution to 3D Game Programming With DirectX11 第11章 习题解答
- Linux图形界面中客户端、服务器、窗口管理器之间的关系
- oracle 之 pl/sql
- Jquery和ajax开发案例之---自动补全输入框