什么是函数式编程? 在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
//Lambda表达式
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) {System.out.println("button clicked");}
});
=>
button.addActionListener(event -> System.out.println("button clicked"));1 传入一个实现某接口的对象不同,我们传入了一段代码块——一个没有名字的函数。 event是参数名,和上面匿名内部类示例中的是同一个参数。-> 将参数和 Lambda 表达式 的主体分开,而主体是用户点击按钮时会运行的一些代码;
2 使用匿名内部类时需要显式地声明参数类型ActionEvent event,而在Lambda表达式中无需指定类型;
1:Runnable noArguments = () -> System.out.println("Hello World"); 2:ActionListener oneArgument = event -> System.out.println("button clicked");3:Runnable multiStatement = () -> { System.out.print("Hello");System.out.println(" World");};4:BinaryOperator<Long> add = (x, y) -> x + y;5:BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
例2-8 ActionListener 接口:接受ActionEvent 类型的参数,返回空
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent event);
}
ActionListener 只有一个抽象方法:actionPerformed,被用来表示行为:接受一个参数,
返回空。记住,由于actionPerformed 定义在一个接口里,因此abstract 关键字不是必需
的。该接口也继承自一个不具有任何方法的父接口:EventListener。这就是函数接口,接口中单一方法的命名并不重要,只要方法签名和Lambda 表达式的类
型匹配即可。可在函数接口中为参数起一个有意义的名字,增加代码易读性,便于更透彻
地理解参数的用途。接口 参数 返回类型 示例
Predicate<T> T boolean 这张唱片已经发行了吗
Consumer<T> T void 输出一个值
Function<T,R> T R 获得Artist 对象的名字
Supplier<T> None T 工厂方法
UnaryOperator<T> T T 逻辑非( !)
BinaryOperator<T> (T, T) T 求两个数的乘积( *)
allArtists.stream().filter(artist -> artist.isFrom("London"));
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream<String> stringStream = Stream.of("A");
collect(toList()):collect(toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。
map:如果有一个函数可以将一种类型的值转换成另外一种类型,map 操作就可以 使用该函数,将一个流中的值转换成一个新的流;
List<String> collected = Stream.of("a", "b", "hello")
.map(string -> UpperCase())
.collect(toList());传给map的Lambda 表达式只接受一个String 类型的参数,返回一个新的String。
Function 接口是只包含一个参数的普通函数接口: T->Function->R
List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1").filter(value -> isDigit(value.charAt(0))).collect(toList());Function 接口是Predicate : T->Predicate-> boolean
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)).flatMap(numbers -> numbers.stream()).collect(toList());
assertEquals(asList(1, 2, 3, 4), together);flatMap 方法的相关函数接口和map 方法的一样,都是Function 接口,只是方法的返回值
限定为Stream 类型罢了
List<Track> tracks = asList(new Track("Bakai", 524),
new Track("Violets for Your Furs", 378),
new Track("Time Was", 451));
Track shortestTrack = tracks.stream().min(Comparatorparing(track -> Length())).get();
(1), shortestTrack);需要传给它一个Comparator 对象。Java 8 提
供了一个新的静态方法comparing,使用它可以方便地实现一个比较器;
comparing 方法是值得的。实际上这个方法接受一个函数并返回另一个函数;
int count = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);
assertEquals(6, count);reducer
的类型是第2 章已介绍过的BinaryOperator
contact:
at(Stream.of(1, 2, 3), Stream.of(4, 5)).forEach(integer -> System.out.print(integer + " "));distinct:
Stream.of(1,2,3,1,2,3).distinct().forEach(System.out::println);peek:peek方法生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行
Stream.of(1, 2, 3, 4, 5).peek(integer -> System.out.println("accept:" + integer)).forEach(System.out::println);skip:skip方法将过滤掉原Stream中的前N个元素,返回剩下的元素所组成的新Stream。如果原Stream的元素个数大于N,将返回原Stream的后(原Stream长度-N)个元素所组成的新Stream;如果原Stream的元素个数小于或等于N,将返回一个空Stream。
Stream.of(1, 2, 3,4,5)
.skip(2)
.forEach(System.out::println);
// 打印结果
// 3,4,5sorted:sorted方法将对原Stream进行排序,返回一个有序列的新Stream。sorterd有两种变体sorted(),sorted(Comparator),前者将默认使用Object.equals(Object)进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。Stream.of(5, 4, 3, 2, 1).sorted().forEach(System.out::println);// 打印结果// 1,2,3,4,5Stream.of(1, 2, 3, 4, 5).sorted().forEach(System.out::println);// 打印结果// 5, 4, 3, 2, 1count
count方法将返回Stream中元素的个数。allMatch
allMatch操作用于判断Stream中的元素是否全部满足指定条件。如果全部满足条件返回true,否则返回false。anyMatch
anyMatch操作用于判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。findAny
findAny操作用于获取含有Stream中的某个元素的Optional,如果Stream为空,则返回一个空的Optional。findFirst
findFirst操作用于获取含有Stream中的第一个元素的Optional,如果Stream为空,则返回一个空的Optional。若Stream并未排序,可能返回含有Stream中任意元素的Optional。limit
limit方法将截取原Stream,截取后Stream的最大长度不能超过指定值N。如果原Stream的元素个数大于N,将截取原Stream的前N个元素;如果原Stream的元素个数小于或等于N,将截取原Stream中的所有元素。noneMatch
noneMatch方法将判断Stream中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回true;否则,返回false.
找出某张专辑上所有乐队的国籍。艺术家列表里既有个人,也有 乐队。利用一点领域知识,假定一般乐队名以定冠词 The 开头。当然这不是绝对的,但也 差不多。
Set<String> origins = Musicians().filter(artist -> Name().startsWith("The")).map(artist -> Nationality()).collect(toSet());
public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); for(Album album : albums) {for (Track track : TrackList()) {if (Length() > 60) {String name = Name();trackNames.add(name);}} }return trackNames;
}public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); albums.stream().forEach(album -> {Tracks().forEach(track -> {if (Length() > 60) {String name = Name();trackNames.add(name);}});});return trackNames;
}public Set<String> findLongTracks(List<Album> albums) {Set<String> trackNames = new HashSet<>();albums.stream().flatMap(album -> Tracks()).filter(track -> Length() > 60).map(track -> Name()).forEach(name -> trackNames.add(name));
return trackNames; }
内部迭代将更多控制权交给了集合类。
和Iterator类似,Stream是一种内部迭代方式。
将Lambda表达式和Stream上的方法结合起来,可以完成很多常见的集合操作。
多次调用流操作:用户也可以选择每一步强制对函数求值,而不是将所有的方法调用链接在一起,但是,最好不要如此操作;
1 编写一个求和函数, 计算流中所有数之和。例如,int addUp(Stream<Integer> numbers);
public static int addUp(Stream<Integer> numbers){duce(0,(acc,x)->acc+x);
}2编写一个函数,接受艺术家列表作为参数,返回一个字符串列表,其中包含艺术家的姓名和国籍
public static List<string> getNameAndOrigin(List<Aritist> aritists){return aritists.stream().flatMap(x-> Stream.Name(), x.getNation())).List());
}3 编写一个函数,接受专辑列表作为参数,返回一个由最多包含3 首歌曲的专辑组成的列表
public static List<Album> getAlbumsWithMostThreeTracks(List<Album> albums){albums.stream().filter(x-> x.getTracks(),size()<=3)。collect(toList());
}2. 迭代。修改如下代码,将外部迭代转换成内部迭代:
int totalMembers = 0;
for (Artist artist : artists) {Stream<Artist> members = Members();totalMembers += unt();
}artists.stream().flatMap(x-&Members()).count();3 计算一个字符串中小写字母的个数(提示:参阅String 对象的chars 方法)
public static int countLowercaseLetters(string str){return str.chars().filter(x->x.isLowerCase).count();
}4 在一个字符串列表中,找出包含最多小写字母的字符串。对于空列表,返回Optional
<String> 对象
public static Optional<String> mostLowercaseString(List<String> strings) {return strings.stream().max(ComparatorparingInt(StringExercises::countLowercaseLetters));}1. 只用reduce 和Lambda 表达式写出实现Stream 上的map 操作的代码,如果不想返回
Stream,可以返回一个List。
public class MapUsingReduce {public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {duce(new ArrayList<O>(), (acc, x) -> {// We are copying data from acc to new list instance. It is very inefficient,// but contract duce method requires that accumulator function does// not mutate its arguments.// llect method could be used to implement more efficient mutable reduction,// but this exercise asks to use reduce method.List<O> newAcc = new ArrayList<>(acc);newAcc.add(mapper.apply(x));return newAcc;}, (List<O> left, List<O> right) -> {// We are copying left to new list to avoid mutating it. List<O> newLeft = new ArrayList<>(left);newLeft.addAll(right);return newLeft;});}}2. 只用reduce 和Lambda 表达式写出实现Stream 上的filter 操作的代码,如果不想返回
Stream,可以返回一个List。
/*** Advanced Exercises Question 2*/
public class FilterUsingReduce {public static <I> List<I> filter(Stream<I> stream, Predicate<I> predicate) {List<I> initial = new ArrayList<>();duce(initial,(List<I> acc, I x) -> {if (st(x)) {// We are copying data from acc to new list instance. It is very inefficient,// but contract duce method requires that accumulator function does// not mutate its arguments.// llect method could be used to implement more efficient mutable reduction,// but this exercise asks to use reduce method explicitly.List<I> newAcc = new ArrayList<>(acc);newAcc.add(x);return newAcc;} else {return acc;}},FilterUsingReduce::combineLists);}private static <I> List<I> combineLists(List<I> left, List<I> right) {// We are copying left to new list to avoid mutating it. List<I> newLeft = new ArrayList<>(left);newLeft.addAll(right);return newLeft;}}
//代码中使用Lambda表达式
Logger logger = new Logger();
if (logger.isDebugEnabled()) {logger.debug("Look at this: " + expensiveOperation());
}=>
Logger logger = new Logger();
logger.debug(() -> "Look at this: " + expensiveOperation());
public void debug(Supplier<String> message) { if (isDebugEnabled()) {());}
}
int 和 Integer—— 前者是基本类型,后者是装箱类型,由于装箱类型是对象,因此在内存中存在额外开销。将基本类型转换为装箱类型,称为装箱,反之则称为拆箱,两者都需要额外的计算开销。 对于需要大量数值运算的算法来说,装箱和拆箱的计算开销,以及装箱类型占用的额外内存,会明显减缓程序的运行速度。为了减小这些性能开销,Stream 类的某些方法对基本类型和装箱类型做了区分,仅对整型、 长整型和双浮点型做了特殊处理,对基本类型做特殊处理的方法在命名上有明确的规范;
如果方法返回类型为基本类型,则 在基本类型前加 To,如· ToLongFunction。
如果参数是基本类型,则不加前缀只 需类型名即可,如 LongFunction。
如果高阶函数使用基本类型,则在操作后加 后缀 To 再加基本类型,如 mapToLong。
总而言之,Lambda 表达式作为参数时,其类型由它的目标类型推导得出,推导过程遵循 如下规则:
比如接口 Carriage 和 Jukebox 都有一个默认方法 rock,虽然各有各的用途。类 MusicalCarriage 同时实现了接口 Jukebox。
public class MusicalCarriage implements Carriage, Jukebox { }编译器会报错
public class MusicalCarriage implements Carriage, Jukebox {@Overridepublic String rock() {return k(); }
}
javac 并不明确应该继承哪个接口中的方法,因此编译器会报错:class Musical Carriage
inherits unrelated defaults for rock() from types Carriage and Jukebox。当然,在类中实现rock 方法就能解决这个问题,
接口和抽象类之间还是存在明显的区别。接口允许多重继承,却没有成员变量;抽象类可以继承成员变量,却不能多重继承。
Optional
artist -> Name()
Artist::getName
标准语法为 Classname::methodName
(name, nationality) -> new Artist(name, nationality)
Artist::new
List<Integer> numbers = asList(1, 2, 3, 4);
List<Integer> sameOrder = numbers.stream().collect(toList());
assertEquals(numbers, sameOrder);
Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
List<Integer> sameOrder = numbers.stream().collect(toList());
// 该断言有时会失败
assertEquals(asList(4, 3, 2, 1), sameOrder);
toSet 和 toCollection
llect(toCollection(TreeSet::new));
//找出成员最多的乐队
public Optional<Artist> biggestGroup(Stream<Artist> artists) { Function<Artist,Long> getCount = artist -> Members().count(); llect(maxBy(comparing(getCount)));
}
partitioningBy,它接受一个流,并将其分成两部分;
将艺术家组成的流分成乐队和独唱歌手两部分;
public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) { llect(partitioningBy(artist -> artist.isSolo()));
}
使用方法引用代替 Lambda 表达式:llect(partitioningBy(Artist::isSolo));
llect(groupingBy(album -> MainMusician()));
期望的输出 为:"[George Harrison, John Lennon, Paul McCartney, Ringo Starr, The Beatles]"
String result = artists.stream().map(Artist::getName).collect(Collectors.joining(", ", "[", "]"));
使用收集器计算每个艺术家的专辑数
public Map<Artist, Long> numberOfAlbums(Stream<Album> albums) { llect(groupingBy(album -> MainMusician(),counting()));
}
使用Map 的computeIfAbsent 方法高效计算斐波那契数列。这里的“高效”是指避免将那
些较小的序列重复计算多次。
package com.insightfullogic.java8.answers.chapter5;import java.util.HashMap;
import java.util.Map;public class Fibonacci {private final Map<Integer,Long> cache;public Fibonacci() {cache = new HashMap<>();cache.put(0, 0L);cache.put(1, 1L);}public long fibonacci(int x) {return cacheputeIfAbsent(x, n -> fibonacci(n-1) + fibonacci(n-2));}}
用一个定制的收集器实现upingBy 方法,不需要提供一个下游收集器,只需实现一个最简单的即可。别看JDK 的源码,这是作弊!提示:可从下面这行代码开始:
public class GroupingBy<T, K> implements Collector<T, Map<K, List>, Map<K,List>>
package com.insightfullogic.java8.answers.chapter5;import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;public class GroupingBy<T, K> implements Collector<T, Map<K, List<T>>, Map<K, List<T>>> {private final static Set<Characteristics> characteristics = new HashSet<>();static {characteristics.add(Characteristics.IDENTITY_FINISH);}private final Function<? super T, ? extends K> classifier;public GroupingBy(Function<? super T, ? extends K> classifier) {this.classifier = classifier;}@Overridepublic Supplier<Map<K, List<T>>> supplier() {return HashMap::new;}@Overridepublic BiConsumer<Map<K, List<T>>, T> accumulator() {return (map, element) -> {K key = classifier.apply(element);List<T> elements = mapputeIfAbsent(key, k -> new ArrayList<>());elements.add(element);};}@Overridepublic BinaryOperator<Map<K, List<T>>> combiner() {return (left, right) -> {right.forEach((key, value) -> {(key, value, (leftValue, rightValue) -> {leftValue.addAll(rightValue);return leftValue;});});return left;};}@Overridepublic Function<Map<K, List<T>>, Map<K, List<T>>> finisher() {return map -> map;}@Overridepublic Set<Characteristics> characteristics() {return characteristics;}}
假设一个元素为单词的流,计算每个单词出现的次数。假设输入如下,则返回值为一个形如[John → 3, Paul → 2, George → 1] 的Map:Stream names = Stream.of(“John”, “Paul”, “George”, “John”,
“Paul”, “John”);
import com.amples.chapter1.Artist;import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;import static java.util.Comparatorparing;public class LongestName {private static Comparator<Artist> byNameLength = comparing(artist -> Name().length());public static Artist byReduce(List<Artist> artists) {return artists.stream().reduce((acc, artist) -> {return (byNameLengthpare(acc, artist) >= 0) ? acc : artist;}).orElseThrow(RuntimeException::new);}public static Artist byCollecting(List<Artist> artists) {return artists.stream().collect(Collectors.maxBy(byNameLength)).orElseThrow(RuntimeException::new);}}
如果已经有一个 Stream 对象,调用它的 parallel 方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用 parallelStream 就能立即获得一个拥有并行能力的流。
在一个四核电脑上,如果有10 张专辑,串行化代码的速度是并行化代码速度的8 倍;如果将专辑数量增至100 张,串行化和并行化速度相当;如果将专辑数量增值10 000 张,则并行化代码的速度是串行化代码速度的2.5 倍。
之前调用 reduce 方法,初始值可以为任意值,为了让其在并行化时能工作正常,初值必须 为组合函数的恒等值。拿恒等值和其他值做 reduce 操作时,其他值保持不变。
比如,使用 reduce操作求和,组合函数为(acc, element) -> acc + element,则其初值必须为0,因 为任何数字加 0,值不变。乘法的话初始值就是1;
reduce 操作的另一个限制是组合操作必须符合结合律。这意味着只要序列的值不变,组 合操作的顺序不重要。
影响并行流性能的主要因素有 5 个
我们可以根据性能的好坏,将核心类库提供的通用数据结构分成以下3 组。
* parallelPrefix,任意给定一个函数,计算数组的和
* parallelSetAll 使用 Lambda 表达式更新数组元素 Arrays.parallelSetAll(values, i -> i);它们改变了传入的数组,而没有创建一个新的数组。
* parallelSort
eg1:
range.map(x -> x * x).sum();
range.parallel().map(x->x*x).sum();
return linkedListOfNumbers.stream().reduce(5, (acc, x) -> x * acc);
5 * numbers.parallelStream().reduce(1, (acc, x) -> x * acc);
例6-11 中的代码把列表中的数字相乘,然后再将所得结果乘以5。顺序执行这段程序没有问题,但并行执行时有一个缺陷,使用流并行化执行该段代码,并修复缺陷。
例6-11 把列表中的数字相乘,然后再将所得结果乘以5,该实现有一个缺陷
public static int multiplyThrough(List<Integer> linkedListOfNumbers) {return linkedListOfNumbers.stream().reduce(5, (acc, x) -> x * acc);
}
public static int multiplyThrough(List<Integer> numbers) {return 5 * numbers.parallelStream().reduce(1, (acc, x) -> x * acc);
}
Logger logger = new Logger();
if (logger.isDebugEnabled()) {
logger.debug("Look at this: " + expensiveOperation());
}Logger logger = new Logger();
logger.debug(() -> "Look at this: " + expensiveOperation());
ThreadLocal<Album> thisAlbum = new ThreadLocal<Album> () {
@Override protected Album initialValue() {
return database.lookupCurrentAlbum();
}
};ThreadLocal<Album> thisAlbum
= ThreadLocal.withInitial(() -> database.lookupCurrentAlbum());
public long countRunningTime() {
long count = 0;
for (Album album : albums) {for (Track track : TrackList()) {count += Length();}
}
return count;
}public long countMusicians() {
long count = 0;
for (Album album : albums) {count += MusicianList().size();
}
return count;
}public long countTracks() {
long count = 0;
for (Album album : albums) {count += TrackList().size();
}
return count;
}=》public long countRunningTime() {
return albums.stream().mapToLong(album -> Tracks().mapToLong(track -> Length()).sum()).sum();
}public long countMusicians() {
return albums.stream().mapToLong(album -> Musicians().count()).sum();
}public long countTracks() {
return albums.stream().mapToLong(album -> Tracks().count()).sum();
}=> public long countFeature(ToLongFunction<Album> function) {return albums.stream().mapToLong(function).sum();
}public long countTracks() {return countFeature(album -> Tracks().count());
}public long countRunningTime() {return countFeature(album -> Tracks().mapToLong(track -> Length()).sum());
}public long countMusicians() {return countFeature(album -> Musicians().count());
}
public static List<String> elementFirstToUppercase(List<String> words) {return words.stream().map(Testing::firstToUppercase).collect(Collectors.<String>toList());
}public static String firstToUppercase(String value) { char firstChar = UpperCase(value.charAt(0));return firstChar + value.substring(1);
}
把处理字符串的的逻辑抽取成一个方法后,就可以测试该方法,把所有的边界情况都覆盖
到。新的测试用例如例7-13 所示。例7-13 测试单独的方法
@Test
public void twoLetterStringConvertedToUppercase() {String input = "ab";String result = Testing.firstToUppercase(input);assertEquals("Ab", result);
}
List<String> s = Arrays.asList("12a3", "2s3", "@b3", "3d2", "6d6");
List<String> strings = s.stream().map(String::toUpperCase).peek(nation -> System.out.println("Found nationality: " + nation)).List());可以断点调试;
命令接收者
public interface Editor {public void save();public void open();public void close();
}我们需要一个统一的接口来概括这些不同的操作,我将这个接口叫作Action,它代表了一个操作。所有的命令都要实现该接口
public interface Action {public void perform();
}现在让每个操作都实现该接口,这些类要做的只是在Action 接口中调用Editor 类中的一个方法
保存操作代理给Editor 方法public class Save implements Action {private final Editor editor;public Save(Editor editor) {this.editor = editor;}@Overridepublic void perform() {editor.save();}
}public class Macro {private final List<Action> actions;public Macro() {actions = new ArrayList<>();}public void record(Action action) {actions.add(action);}public void run() {actions.forEach(Action::perform);}
}Macro macro = new Macro();
d(new Open(editor));
d(new Save(editor));
d(new Close(editor));
macro.run();使用Lambda 表达式构建宏
Macro macro = new Macro();
d(() -> editor.open());
d(() -> editor.save());
d(() -> editor.close());
macro.run();使用方法引用构建宏
Macro macro = new Macro();
d(editor::open);
d(editor::save);
d(editor::close);
macro.run();在核心Java 中,已经有一个和Action 接口结构一致的函数接口——Runnable。
我们可以在实现上述宏程序中直接使用该接口
文件压缩就是一个很好的例子。我们提供给用户各种压缩文件的方式,可以使用zip 算法,也可以使用gzip 算;定义压缩数据的策略接口
public interface CompressionStrategy {public OutputStream compress(OutputStream data) throws IOException;
}有两个类实现了该接口,分别代表gzip 和ZIP 算法
public class GzipCompressionStrategy implements CompressionStrategy {@Overridepublic OutputStream compress(OutputStream data) throws IOException {return new GZIPOutputStream(data);}
}public class ZipCompressionStrategy implements CompressionStrategy {@Overridepublic OutputStream compress(OutputStream data) throws IOException {return new ZipOutputStream(data);}
}Compressor 类,有一个compress方法,读入文件,压缩后输出。它的构造函数有一个CompressionStrategy 参数,调用代码可以在运行期使用该参数决定使用哪种压缩策略;public class Compressor {private final CompressionStrategy strategy;public Compressor(CompressionStrategy strategy) {this.strategy = strategy;}public void compress(Path inFile, File outFile) throws IOException {try (OutputStream outStream = new FileOutputStream(outFile)) {py(inFile, strategypress(outStream));}}
}Compressor gzipCompressor = new Compressor(new GzipCompressionStrategy());
gzipCompressorpress(inFile, outFile);Compressor zipCompressor = new Compressor(new ZipCompressionStrategy());
zipCompressorpress(inFile, outFile);==》
Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
gzipCompressorpress(inFile, outFile);Compressor zipCompressor = new Compressor(ZipOutputStream::new);
zipCompressorpress(inFile, outFile);
观察的对象是月球! NASA 和外星人都对登陆
到月球上的东西感兴趣,都希望可以记录这些信息。NASA 希望确保阿波罗号上的航天员
成功登月;外星人则希望在NASA 注意力分散之时进犯地球。定义观察者的API, 这里我将观察者称作LandingObserver。它只有一个observeLanding 方法,当有东西登陆到月球上时会调用该方法
public interface LandingObserver {public void observeLanding(String name);
}被观察者是月球Moon,它持有一组LandingObserver 实例,有东西着陆时会通知这些观察
者,还可以增加新的LandingObserver 实例观测Moon 对象
public class Moon {private final List<LandingObserver> observers = new ArrayList<>();public void land(String name) {for (LandingObserver observer : observers) {observer.observeLanding(name);}
}public void startSpying(LandingObserver observer) {observers.add(observer);}
}我们有两个具体的类实现了LandingObserver 接口,分别代表外星人和NASA检测着陆情况
public class Aliens implements LandingObserver {@Overridepublic void observeLanding(String name) {if (ains("Apollo")) {System.out.println("They're distracted, lets invade earth!");}}
}public class Nasa implements LandingObserver {@Overridepublic void observeLanding(String name) {if (ains("Apollo")) {System.out.println("We made it!");}}
}Moon moon = new Moon();
moon.startSpying(new Nasa());
moon.startSpying(new Aliens());
moon.land("An asteroid");
moon.land("Apollo 11");=》 Moon moon = new Moon();
moon.startSpying(name -> {if (ains("Apollo"))System.out.println("We made it!");
});
moon.startSpying(name -> {if (ains("Apollo"))System.out.println("They're distracted, lets invade earth!");
});
moon.land("An asteroid");
moon.land("Apollo 11");
假设我们是一家银行,需要对公众、公司和职员放贷。放贷程序大体一致——验明身份、信用记录和收入记录;抽象类LoanApplication 来控制算法结构,该类包含一些贷款调查结果报告的通用代码public abstract class LoanApplication {public void checkLoanApplication() throws ApplicationDenied {checkIdentity();checkCreditHistory();checkIncomeHistory();reportFindings();}
protected abstract void checkIdentity() throws ApplicationDenied;
protected abstract void checkIncomeHistory() throws ApplicationDenied;
protected abstract void checkCreditHistory() throws ApplicationDenied;
private void reportFindings(){}
}
根据不同的申请人,有不同的类:CompanyLoanApplication、PersonalLoanApplication 和EmployeeLoanApplication==> 模板方法模式真正要做的是将一组方法调用按一定顺序组织起来。如果用函数接口表
示函数,用Lambda 表达式或者方法引用实现这些接口,相比使用继承构建算法,就会得到
极大的灵活性。让我们看看如何使用这种方式实现LoanApplication 算法
public class LoanApplication {private final Criteria identity;private final Criteria creditHistory;private final Criteria incomeHistory;public LoanApplication(Criteria identity,Criteria creditHistory,Criteria incomeHistory) { this.identity = ditHistory = creditHistory;this.incomeHistory = incomeHistory;
}public void checkLoanApplication() throws ApplicationDenied {identity.check();creditHistory.check();incomeHistory.check();reportFindings();
}
private void reportFindings() {}这里没有使用一系列的抽象方法, 而是多出一些属性:identity、creditHistory 和incomeHistory。每一个属性都实现了函数接口Criteria,该接口检查一项标准,如果不达标就抛出一个问题域里的异常。我们也可以选择从check 方法返回一个类来表示成功或失败,但是沿用异常更加符合先前的实现public interface Criteria {public void check() throws ApplicationDenied;
}
略
每章的练习答案:
本文发布于:2024-02-02 11:48:55,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170684573643596.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |