This final example will create truly generic code using the adapter approach described in the previous section. The example began as an attempt to create a sum over a sequence of elements (of any type that can be summed), but evolved into performing general operations using a functional style of programming.
If you just look at the process of trying to add objects, you can see that this is a case where we have common operations across classes, but the operations are not represented in any base class that we can specify—sometimes you can even use a'+' operator, and other times there may be some kind of "add"
method. This is generally the situation that you encounter when trying to write generic code, because you want the code to apply across multiple classes—especially, as in this case, multiple classes that already exist and that we have no ability to "fix." Even if you were to narrow this case to subclasses of Number, that superclass doesn't include anything about "addability."
The solution is to use the Strategy design pattern, which produces more elegant code because it completely isolates "the thing that changes" inside of a function object(You will sometimes see these called functors. I will use the term function object rather than functor, as the term "functor" has a specific and different meaning in mathematics).A function object is an object that in some way behaves like a function—typically, there's one method of interest (in languages that support operator overloading, you can make the call to this method look like an ordinary method call). The value of function objects is that, unlike an ordinary method, they can be passed around, and they can also have state that persists across calls. Of course, you can accomplish something like this with any method in a class, but (as with any design pattern) the function object is primarily distinguished by its intent. Here the intent is to create something that behaves like a single method that you can pass around; thus it is closely coupled with—and sometimes indistinguishable from—the Strategy design pattern.
As I've found with a number of design patterns, the lines get kind of blurry here: We are creating function objects which perform adaptation, and they are being passed into methods to be used as strategies.
Taking this approach, I added the various kinds of generic methods that I had originally set out to create, and more. Here is the result:
//: generics/Functional.java import java.math.*; import java.util.concurrent.atomic.*; import java.util.*; import static net.mindview.util.Print.*; // Different types of function objects: interface Combiner<T> { T combine(T x, T y); } interface UnaryFunction<R,T> { R function(T x); } interface Collector<T> extends UnaryFunction<T,T> { T result(); // Extract result of collecting parameter } interface UnaryPredicate<T> { boolean test(T x); } public class Functional { // Calls the Combiner object on each element to combine // it with a running result, which is finally returned: public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) { Iterator<T> it = seq.iterator(); if(it.hasNext()) { T result = it.next(); while(it.hasNext()) result = combiner.combine(result, it.next()); return result; } // If seq is the empty list: return null; // Or throw exception } // Take a function object and call it on each object in // the list, ignoring the return value. The function // object may act as a collecting parameter, so it is // returned at the end. public static <T> Collector<T> forEach(Iterable<T> seq, Collector<T> func) { for(T t : seq) func.function(t); return func; } // Creates a list of results by calling a // function object for each object in the list: public static <R,T> List<R> transform(Iterable<T> seq, UnaryFunction<R,T> func) { List<R> result = new ArrayList<R>(); for(T t : seq) result.add(func.function(t)); return result; } // Applies a unary predicate to each item in a sequence, // and returns a list of items that produced "true": public static <T> List<T> filter(Iterable<T> seq, UnaryPredicate<T> pred) { List<T> result = new ArrayList<T>(); for(T t : seq) if(pred.test(t)) result.add(t); return result; } // To use the above generic methods, we need to create // function objects to adapt to our particular needs: static class IntegerAdder implements Combiner<Integer> { public Integer combine(Integer x, Integer y) { return x + y; } } static class IntegerSubtracter implements Combiner<Integer> { public Integer combine(Integer x, Integer y) { return x - y; } } static class BigDecimalAdder implements Combiner<BigDecimal> { public BigDecimal combine(BigDecimal x, BigDecimal y) { return x.add(y); } } static class BigIntegerAdder implements Combiner<BigInteger> { public BigInteger combine(BigInteger x, BigInteger y) { return x.add(y); } } static class AtomicLongAdder implements Combiner<AtomicLong> { public AtomicLong combine(AtomicLong x, AtomicLong y) { // Not clear whether this is meaningful: return new AtomicLong(x.addAndGet(y.get())); } } // We can even make a UnaryFunction with an "ulp" // (Units in the last place): static class BigDecimalUlp implements UnaryFunction<BigDecimal,BigDecimal> { public BigDecimal function(BigDecimal x) { return x.ulp(); } } static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T> { private T bound; public GreaterThan(T bound) { this.bound = bound; } public boolean test(T x) { return x.compareTo(bound) > 0; } } static class MultiplyingIntegerCollector implements Collector<Integer> { private Integer val = 1; public Integer function(Integer x) { val *= x; return val; } public Integer result() { return val; } } public static void main(String[] args) { // Generics, varargs & boxing working together: List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7); Integer result = reduce(li, new IntegerAdder()); print(result); result = reduce(li, new IntegerSubtracter()); print(result); print(filter(li, new GreaterThan<Integer>(4))); print(forEach(li, new MultiplyingIntegerCollector()).result()); print(forEach(filter(li, new GreaterThan<Integer>(4)), new MultiplyingIntegerCollector()).result()); MathContext mc = new MathContext(7); List<BigDecimal> lbd = Arrays.asList( new BigDecimal(1.1, mc), new BigDecimal(2.2, mc), new BigDecimal(3.3, mc), new BigDecimal(4.4, mc)); BigDecimal rbd = reduce(lbd, new BigDecimalAdder()); print(rbd); print(filter(lbd, new GreaterThan<BigDecimal>(new BigDecimal(3)))); // Use the prime-generation facility of BigInteger: List<BigInteger> lbi = new ArrayList<BigInteger>(); BigInteger bi = BigInteger.valueOf(11); for(int i = 0; i < 11; i++) { lbi.add(bi); bi = bi.nextProbablePrime(); } print(lbi); BigInteger rbi = reduce(lbi, new BigIntegerAdder()); print(rbi); // The sum of this list of primes is also prime: print(rbi.isProbablePrime(5)); List<AtomicLong> lal = Arrays.asList( new AtomicLong(11), new AtomicLong(47), new AtomicLong(74), new AtomicLong(133)); AtomicLong ral = reduce(lal, new AtomicLongAdder()); print(ral); print(transform(lbd,new BigDecimalUlp())); } } /* Output: 28 -26 [5, 6, 7] 5040 210 11.000000 [3.300000, 4.400000] [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] 311 true 265 [0.000001, 0.000001, 0.000001, 0.000001] *///:~
I begin by defining interfaces for different types of function objects. These were created on demand, as I developed the different methods and discovered the need for each. The Combiner class was suggested by an anonymous contributor to one of the articles posted on my Web site. The Combiner abstracts away the specific detail of trying to add two objects, and just says that they are being combined somehow. As a result, you can see that IntegerAdder and IntegerSubtracter can be types of Combiner.
A UnaryFunction takes a single argument and produces a result; the argument and result need not be of the same type. A Collector is used as a "collecting parameter," and you can extract the result when you're finished. A UnaryPredicate produces a boolean result. There are other types of function objects that can be defined, but these are enough to make the point.
The Functional class contains a number of generic methods that apply function objects to sequences. reduce( ) applies the function in a Combiner to each element of a sequence in order to produce a single result.
forEach( ) takes a Collector and applies its function to each element, ignoring the result of each function call. This can be called just for the side effect (which wouldn't be a "functional" style of programming but can still be useful), or the Collector can maintain internal state to become a collecting parameter, as is the case in this example.
transform( ) produces a list by calling a UnaryFunction on each object in the sequence and capturing the result.
Finally, filter( ) applies a UnaryPredicate to each object in a sequence and stores the ones that produce true in a List, which it returns.
You can define additional generic functions. The C++ STL, for example, has lots of them. The problem has also been solved in some open-source libraries, such as the JGA (Generic Algorithms for Java).
In C++, latent typing takes care of matching up operations when you call functions, but in Java we need to write the function objects to adapt the generic methods to our particular needs. So the next part of the class shows various different implementations of the function objects. Note, for example,that IntegerAdder and BigDecimalAdder solve the same problem-adding two objects—by calling the appropriate operations for their particular type. So that's the Adapter pattern and Strategy pattern combined.
In main( ), you can see that in each method call, a sequence is passed along with the appropriate function object. Also, a number of the expressions can get fairly complex, such as:
forEach(filter(li, new GreaterThan<Integer>(4)),new MultiplyingIntegerCollector()).result()
This produces a list by selecting all elements in li that are greater than 4, and then applies the MultiplyingIntegerCollector( ) to the resulting list and extracts the result( ). I won't explain the details of the rest of the code other than to say that you can probably figure it out by walking through it.
相关推荐
《Thinking in Java》是Bruce Eckel的经典之作,第四版涵盖了Java编程语言的广泛主题,适合初学者和有经验的程序员。这本书深入浅出地讲解了Java的核心概念和技术,旨在帮助读者建立坚实的编程基础,并理解面向对象...
《Thinking in Java》是Java编程领域的一本经典著作,由Bruce Eckel撰写,深受程序员喜爱。这本书分为第三版和第四版,提供了英文版和中文版,适合不同语言背景的学习者。书中内容详实且深入,从基础知识到高级概念...
很抱歉,但根据您给出的信息,这似乎是一个音乐文件列表,而非与"Thinking in Java"相关的IT知识内容。"Thinking in Java"是一本著名的编程书籍,通常与Java编程语言的学习和实践相关。如果您的目标是获取这方面的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它深入浅出地介绍了Java语言的核心概念和技术。这本书通过实例代码来讲解理论,使读者能够更好地理解和掌握Java编程。在这个压缩包中,我们很可能会找到与书中的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它涵盖了Java语言的核心概念和技术,深受程序员和初学者喜爱。这本书分为第三版和第四版,每个版本都有其独特的知识点和更新内容。 第三版是针对Java 2 Platform...
《Thinking in Java(英文原版第4版)》作为一本经典Java编程思想书籍,其内容涵盖了面向对象的叙述方式,并针对Java SE5/6版本新增了示例和章节。本书适合作为初学者的入门教材,同时也包含了足够的深度,适合专业...
《Thinking in Java》会详细解释如何使用这些集合以及迭代器(Iterator)和泛型(Generics)的概念,这些对于编写高效且可维护的代码至关重要。 多线程是并发编程的基础,Java提供了强大的支持。书中会讨论线程的...
### 《Thinking in Java》第四版关键知识点综述 #### 一、书籍概述与评价 《Thinking in Java》第四版是一本备受推崇的经典Java编程教材,由Bruce Eckel撰写,适用于初学者及进阶读者。本书自出版以来,便成为众多...
《Thinking in Java》是Bruce Eckel的经典之作,第四版更是深受全球Java开发者喜爱的教材。这本书深入浅出地讲解了Java编程语言的核心概念和技术,旨在为初学者提供全面且深入的Java学习指导。 首先,书中的第一章...
《Thinking in Java 4th Edition with Annotated Solution Guide》是一本经典的Java编程教材,由Bruce Eckel撰写。这本书深入浅出地介绍了Java编程的核心概念和技术,对于初学者和经验丰富的开发者来说,都是一个...
《Thinking in Java 4th 习题答案》涵盖了多个关键的Java编程概念和技术,包括深入的容器理解、输入/输出(I/O)处理、枚举类型(Enumerated Types)、注解(Annotations)以及并发编程(Concurrency)。这些章节是Java学习...
《Thinking in Java》是Java编程领域的一本经典之作,由Bruce Eckel撰写,深受程序员喜爱。这本书深入浅出地介绍了Java语言的核心概念和技术,旨在帮助读者建立起坚实的基础,并提升编程思维能力。书中不仅包含了...
Java 泛型详解 Java 中的泛型是 Java 5(JDK 1.5)中引入的一项新特性,旨在解决类型安全和代码重用的问题。泛型允许程序员对类型进行抽象,使得代码更加灵活和可维护。 泛型的优点: 1. 类型安全:泛型可以在...
《Thinking In Java》是Bruce Eckel的经典之作,它深入浅出地介绍了Java编程语言的核心概念和技术,被广大程序员视为学习Java的权威指南。第四版更是加入了更多现代Java特性,如Generics、Annotations等,使读者能够...
《Thinking in Java》是Bruce Eckel的经典著作,它在Java编程领域享有极高的声誉,是许多初学者和专业开发者的必备参考书籍。该书的第三版中文电子版PDF提供了全面而深入的Java学习资源,旨在帮助读者理解Java语言的...
本资料 "[Java泛型和集合].(Java.Generics.and.Collections).Maurice.Naftalin&Philip.Wadler.文字版" 由知名专家Maurice Naftalin和Philip Wadler编著,提供了关于这些主题的深入理解。 **Java泛型** 是自Java...