Comparator byWeight = new Comparator() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
Comparator byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
//第一个Lambda表达式具有一个String类型的参数并返回一个int。Lambda没有return语句,因为已经隐含了return
(String s) -> s.length()
//第二个Lambda 表达式有一个 Apple 类型的 参数并返回一 个boolean(苹 果的重量是否 超过150克)
(Apple a) -> a.getWeight() > 150
//第三个Lambda表达式具有两个int类型的参数而没有返回值(void返回)。注意Lambda表达式可以包含多行语句,这里是两行
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x+y);
}
//第四个Lambda表达式没有参数,返回一个int
() -> 42
//第五个Lambda表达式具有两个Apple类型的参数,返回一个int:比较两个Apple的重量
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
() -> {}
() -> "Raoul"
() -> {return "Mario";}
//布尔表达式
(List list) -> list.isEmpty()
//创建对象
() -> new Apple(10)
//消费一个对象
(Apple a) -> {
System.out.println(a.getWeight());
}
//从一个对象中选择/抽取
(String s) -> s.length()
//组合两个值
(int a, int b) -> a * b
//比较两个对象
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
@FunctionalInterface注解是标注这个类是函数式接口类,通常只做标识提醒用,无任何其他实质性的作用,@FunctionalInterface不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是@Override 标注表示方法被重写了。
为了总结关于函数式接口和Lambda的讨论,表总结了一些使用案例、Lambda的例子,以 及可以使用的函数式接口。
1)流和集合
2)流只能遍历一次
集合和流的另一个关键区别在于它们遍历数据的方式
3)外部迭代和内部迭代
List names = menu.stream()
.map(Dish::getName)
.collect(toList());
帮助理解区别
以下是for-each迭代
你:“索菲亚,我们把玩具收起来吧。地上还有玩具吗?”
索菲亚:“有,球。”
你:“好,把球放进盒子里。还有吗?”索菲亚:“有,那是我的娃娃。”
你:“好,把娃娃放进盒子里。还有吗?”
索菲亚:“有,有我的书。”
你:“好,把书放进盒子里。还有吗?”
索菲亚:“没了,没有了。”
你:“好,我们收好啦。”
以下是流迭代
你:“索菲亚,我们把玩具收起来吧。把你身边的玩具都给我”
索菲亚:“好的。”
你:“好,我们收好啦。”
4)流操作
1)谓词筛选-filter
Streams接口支持filter方法(你现在应该很熟悉了)。该操作会接受一个谓词(一个返回 boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
例如
List vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
2)筛选各异的元素(其实就是去重)-distinct
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
3)截短流(其实就类似于mysql的分页)-limit
List dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
4)跳过元素-skip- 和limit互补
List dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
1)流的扁平化-flatMap
List uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
1)anyMatch-流中是否有一个元素能匹配给定的谓词
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
2)allMatch-看流中的元素是否都能匹配给定的谓词
3)noneMatch-和allMatch相对,即都不能匹配
4)findAny-返回当前流中的任意元素
Optional dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny();
isPresent()将在Optional包含值的时候返回true, 否则返回false。
ifPresent(Consumer block)会在值存在的时候执行给定的代码块。它让你传递一个接收T类型参数,并返回void的Lambda 表达式。
T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
T orElse(T other)会在值存在时返回值,否则返回一个默认值。
5)findFirst-查找第一个元素
List someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
1)元素求和
int sum = 0;
for (int x : numbers) {
sum += x;
}
private static void useReduce() {
logger.info("[useReduce]用法");
List nums = Arrays.asList(1,2,3,6);
//int sum = nums.parallelStream().reduce(0,(a,b) -> a + b);
int sum = nums.parallelStream().reduce(0,Integer::sum);
int product = nums.parallelStream().reduce(1,(a,b) -> a * b);
System.out.println(sum);
System.out.println(product);
}
2) 最大值和最小值
Optional max = numbers.stream().reduce(Integer::max);
Optional min = numbers.stream().reduce(Integer::min);
3) 算出集合中有多少元素
Integer count = nums.parallelStream()
.map(d -> 1)
.reduce(0, Integer::sum);
System.out.println(count);
4) 总结
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。
(7) 所有交易中,最高的交易额是多少? (8) 找到交易额最小的交易。
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
public class Trader{
private final String name;
private final String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
public String getName(){
return this.name;
}
public String getCity(){
return this.city;
}
public String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}
public class Transaction{
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value){
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader(){
return this.trader;
}
public int getYear(){
return this.year;
}
public int getValue(){
return this.value;
}
public String toString(){
return "{" + this.trader + ", " +
"year: "+this.year+", " +
"value:" + this.value +"}";
}
}
6.1 原始类型流特化
1、映射到数值流
这里,mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream (而不是一个Stream)。然后你就可以调用IntStream接口中定义的sum方法,对卡 路里求和了!请注意,如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如 max、min、average等。
2、转换回对象流
一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能 产生原始整数: IntStream 的 map 操作接受的 Lambda 必须接受 int 并返回 int (一个 IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream 接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个 Integer),可以使用boxed方法
3、默认值OptionalInt
求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最 大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢? Optional可以用 Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类 型特化版本:OptionalInt、OptionalDouble和OptionalLong。
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
//如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);
6.2 数值范围
假设你想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围: range和rangeClosed。
6.3 数值流应用:勾股数
Stream pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b ->
new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
);
pythagoreanTriples.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
7.1 由值创建流-Stream.of/empty
Stream stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
Stream emptyStream = Stream.empty();
7.2 由数组创建流-Arrays.stream
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
//这里stream里面必须放int[]才能调出sum()方法
7.3 由文件生成流java.nio.file.Files
long uniqueWords = 0;
try (Stream lines =
Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
}
你可以使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,你 可以对line调用split方法将行拆分成单词。应该注意的是,你该如何使用flatMap产生一个扁 平的单词流,而不是给每一行生成一个单词流。最后,把distinct和count方法链接起来,数 数流中有多少各不相同的单词。
7.4 由函数生成流:创建无限流
1、迭代-Stream.iterate
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
一般来说,在需要依次生成一系列值的时候应该使用iterate,比如一系列日期:1月31日, 2月1日,依此类推。
测验5.4:斐波纳契元组序列
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55…
Stream.iterate(new int[]{0, 1},
t -> new int[]{t[1],t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);
2、生成-Stream.generate
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
List transactions =
transactionStream.collect(Collectors.toList());
1)count/counting
long howManyDishes = menu.stream().collect(Collectors.counting());
//这还可以写得更为直接:
long howManyDishes = menu.stream().count();
2)找流中的最大值和最小值 --maxBy
Comparator dishCaloriesComparator =
Comparator.comparingInt(Dish::getCalories);
Optional mostCalorieDish =
menu.stream()
.collect(maxBy(dishCaloriesComparator));
3)汇总/平均数Collectors.summingInt等
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
Collectors.summingLong和Collectors.summingDouble方法的作用完全一样,可以用 于求和字段为long或double的情况。
double avgCalories =
menu.stream().collect(averagingInt(Dish::getCalories));
IntSummaryStatistics menuStatistics =
menu.stream().collect(summarizingInt(Dish::getCalories));
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}
4) 连接字符串 -- joining
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon
Map> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}
public enum CaloricLevel { DIET, NORMAL, FAT }
Map> dishesByCaloricLevel = menu.stream().collect(
Collectors.groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} ));
1)多级分组
Map>> dishesByTypeCaloricLevel =
menu.stream().collect(
Collectors.groupingBy(Dish::getType,
Collectors.groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} )
)
);
{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]}, FISH={DIET=[prawns], NORMAL=[salmon]}, OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
一般来说,把groupingBy看作“桶”比较容易明白。第一个groupingBy给每个键建立了 一个桶。然后再用下游的收集器去收集每个桶中的元素,以此得到n级分组。
分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函 数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以 分为两组——true是一组,false是一组。
Map> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian));
List vegetarianDishes =
menu.stream().filter(Dish::isVegetarian).collect(toList());
{false=[pork, beef, chicken, prawns, salmon], true=[french fries, rice, season fruit, pizza]}
1) 分区的优势
Map>> vegetarianDishesByType =
menu.stream().collect(
partitioningBy(Dish::isVegetarian,
groupingBy(Dish::getType)));
{false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}, true={OTHER=[french fries, rice, season fruit, pizza]}}
Map mostCaloricPartitionedByVegetarian =
menu.stream().collect(
partitioningBy(Dish::isVegetarian,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
{false=pork, true=pizza}
1) 自己的ToListCollector方法
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import static java.util.stream.Collector.Characteristics.*;
public class ToListCollector implements Collector, List> {
@Override
public Supplier> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer, T> accumulator() {
return List::add;
}
@Override
public Function, List> finisher() {
return Function.indentity();
}
@Override
public BinaryOperator> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Set characteristics() {
return Collections.unmodifiableSet(EnumSet.of(
IDENTITY_FINISH, CONCURRENT));
}
}
1)parallel方法:
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel()
.reduce(0L, Long::sum);
}
2) 当然也可以转回来-sequential方法
stream.parallel()
.filter(...)
.sequential()
.map(...)
.parallel()
.reduce();
并行流内部使用了默认的ForkJoinPool(7.2节会进一步讲到分支/合并框架),它默认的 线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().availableProcessors()得到的。 但是你可以通过系统属性 java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个 并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值。
* 并行处理的性能-流的数据源和可分解性
按照可分解性总结了一些流数据源适不适于并行。
> 分支/合并框架的目的是以递归方式将可以并行的任务拆分成更小的任务,然后将每个子任 务的结果合并起来生成整体结果。它是ExecutorService接口的一个实现,它把子任务分配给 线程池(称为ForkJoinPool)中的工作线程。
1) 使用 RecursiveTask
要把任务提交到这个池,必须创建RecursiveTask的一个子类,其中R是并行化任务(以 及所有子任务)产生的结果类型,或者如果任务不返回结果,则是RecursiveAction类型(当 然它可能会更新其他非局部机构)。
protected abstract R compute();
上面的方法实现大概如下:
if (任务足够小或不可分) {
顺序计算该任务
} else {
将任务分成两个子任务
递归调用本方法,拆分每个子任务,等待所有子任务完成
合并每个子任务的结果
}
Spliterator是Java 8中加入的另一个新接口;这个名字代表“可分迭代器”(splitable iterator)。和Iterator一样,Spliterator也用于遍历数据源中的元素,但它是为了并行执行 而设计的。
Java 8已经为集合框架中包含的所有数据结构提供了一个 默认的Spliterator实现。集合实现了Spliterator接口,接口提供了一个spliterator方法。 这个接口定义了若干方法,如下面的代码清单所示。
public interface Spliterator {
boolean tryAdvance(Consumer super T> action);
Spliterator trySplit();
long estimateSize();
int characteristics();
}
1) 拆分过程
将Stream拆分成多个部分的算法是一个递归过程,第一步是对第一个 Spliterator调用trySplit,生成第二个Spliterator。第二步对这两个Spliterator调用 trysplit,这样总共就有了四个Spliterator。这个框架不断对Spliterator调用trySplit 直到它返回null,表明它处理的数据结构不能再分割,如第三步所示。最后,这个递归拆分过 程到第四步就终止了,这时所有的Spliterator在调用trySplit时都返回了null。
1) 改善代码的可读性
通常的理解是,“别人理解这段代码的难易程度”
利用Lambda表达式、方法引用以及Stream改善程序代码的 可读性:
重构代码,用Lambda表达式取代匿名类
用方法引用重构Lambda表达式
用Stream API重构命令式的数据处理
2) 从匿名类到 Lambda 表达式的转换
i、 Runnable
这段代码及其对应的Lambda表达式实现如下:
//传统方法
Runnable r1 = new Runnable(){
public void run(){
System.out.println("Hello");
}
};
//Java8
Runnable r2 = () -> System.out.println("Hello");
如下:
int a = 10;
Runnable r1 = () -> {
int a = 2;
System.out.println(a);
};
3) 从 Lambda 表达式到方法引用的转换
Lambda表达式非常适用于需要传递代码片段的场景。不过,为了改善代码的可读性,也请 尽量使用方法引用。因为方法名往往能更直观地表达代码的意图。
i、提取方法
Map> dishesByCaloricLevel =
menu.stream()
.collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}));
Map> dishesByCaloricLevel =
menu.stream().collect(groupingBy(Dish::getCaloricLevel));
public class Dish{
…
public CaloricLevel getCaloricLevel(){
if (this.getCalories() <= 400) return CaloricLevel.DIET;
else if (this.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}
}
ii、静态辅助方法,比如comparing、maxBy。
比如
inventory.sort(
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing(Apple::getWeight));
再比如,
很多通用的归约操作,比如sum、maximum,都有内建的辅助方法可以和方法引用结 合使用。比如,在我们的示例代码中,使用Collectors接口可以轻松得到和或者最大值
int totalCalories =
menu.stream().map(Dish::getCalories)
.reduce(0, (c1, c2) -> c1 + c2);
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
4) 从命令式的数据处理切换到 Stream
比如
List dishNames = new ArrayList<>();
for(Dish dish: menu){
if(dish.getCalories() > 300){
dishNames.add(dish.getName());
}
}
menu.parallelStream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.collect(toList());
对设计经验的归纳总结被称为设计模式①。设计软件时,如果你愿意,可以复用这些方式方 法来解决一些常见问题。这看起来像传统建筑工程师的工作方式,对典型的场景(比如悬挂桥、 拱桥等)都定义有可重用的解决方案。
1) 策略模式
策略模式包含三部分内容:
假设希望验证输入的内容是否根据标准进行了恰当的格式化(比如只包含小写字母或 数字)。
public interface ValidationStrategy {
boolean execute(String s);
}
public class IsAllLowerCase implements ValidationStrategy {
public boolean execute(String s){
return s.matches("[a-z]+");
}
}
public class IsNumeric implements ValidationStrategy {
public boolean execute(String s){
return s.matches("\\d+");
}
}
public class Validator{
private final ValidationStrategy strategy;
public Validator(ValidationStrategy v){
this.strategy = v;
}
public boolean validate(String s){
return strategy.execute(s);
}
}
//使用
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa");
Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
boolean b2 = lowerCaseValidator.validate("bbbb");
其实ValidationStrategy已经是一个函数接口,与Predicate
具有同样的函数描述
Validator numericValidator =
new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaa");
Validator lowerCaseValidator =
new Validator((String s) -> s.matches("\\d+"));
boolean b2 = lowerCaseValidator.validate("bbbb");
2) 模板方法
模板方法模式在你“希望使用这个算法,但是需要对其中的某些行进行改进,才能达到希望的效果” 时是非常有用的。
假设需要编写一个简单的在线银行 应用。通常,用户需要输入一个用户账户,之后应用才能从银行的数据库中得到用户的详细信息, 最终完成一些让用户满意的操作。不同分行的在线银行应用让客户满意的方式可能还略有不同, 比如给客户的账户发放红利,或者仅仅是少发送一些推广文件。你可能通过下面的抽象类方式来 实现在线银行应用:
abstract class OnlineBanking {
public void processCustomer(int id){
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
abstract void makeCustomerHappy(Customer c);
}
向processCustomer方法引入了第二个参数,它是一个Consumer类型的参数,与前文定义的makeCustomerHappy的特征保持一致:
public void processCustomer(int id, Consumer makeCustomerHappy){
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}
new OnlineBankingLambda().processCustomer(1337, (Customer c) ->
System.out.println("Hello " + c.getName());
3) 观察者模式
观察者模式是一种比较常见的方案,某些事件发生时(比如状态转变),如果一个对象(通 常我们称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。
好几家报纸机构,比如《纽约时报》《卫报》以及《世 界报》都订阅了新闻,他们希望当接收的新闻中包含他们感兴趣的关键字时,能得到特别通知。
下面是原始的方式实现新闻通知,根据新闻的内容通知不同的观察者(新闻机构)
interface Observer {
void notify(String tweet);
}
class NYTimes implements Observer{
public void notify(String tweet) {
if(tweet != null && tweet.contains("money")){
System.out.println("Breaking news in NY! " + tweet);
}
}
}
class Guardian implements Observer {
public void notify(String tweet) {
if(tweet != null && tweet.contains("queen")){
System.out.println("Yet another news in London... " + tweet);
}
}
}
class LeMonde implements Observer{
public void notify(String tweet) {
if(tweet != null && tweet.contains("wine")){
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}
interface Subject{
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
class Feed implements Subject {
private final List observers = new ArrayList<>();
public void registerObserver(Observer o) {
this.observers.add(o);
}
public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers("The queen said her favourite book is Java 8 in Action!");
这个时候Guardian会收到消息,即打印内容
以上是观察者模式的一个完整的步骤,但是应该也能发现,依然需要定义很多的方法去实现观察者接口,所以可以用Lambda表达式来简化他们。
f.registerObserver((String tweet) -> {
if(tweet != null && tweet.contains("money")){
System.out.println("Breaking news in NY! " + tweet);
}
});
f.registerObserver((String tweet) -> {
if(tweet != null && tweet.contains("queen")){
System.out.println("Yet another news in London... " + tweet);
}
});
4) 责任链模式
责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要 在完成一些工作之后,将结果传递给另一个对象,这个对象接着做一些工作,再转交给下一个处 理对象,以此类推。
代码示例
public abstract class ProcessingObject {
protected ProcessingObject successor;
public void setSuccessor(ProcessingObject successor){
this.successor = successor;
}
public T handle(T input){
T r = handleWork(input);
if(successor != null){
return successor.handle(r);
}
return r;
}
abstract protected T handleWork(T input);
}
handle方法提供了如何进行 工作处理的框架。不同的处理对象可以通过继承ProcessingObject类,提供handleWork方法 来进行创建。
public class HeaderTextProcessing extends ProcessingObject {
public String handleWork(String text){
return "From Raoul, Mario and Alan: " + text;
}
}
public class SpellCheckerProcessing extends ProcessingObject {
public String handleWork(String text){
return text.replaceAll("labda", "lambda");
}
}
ProcessingObject p1 = new HeaderTextProcessing();
ProcessingObject p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);
String result = p1.handle("Aren't labdas really sexy?!!");
System.out.println(result);
打印结果为:打印输出“From Raoul, Mario and Alan: Aren't lambdas really sexy?!!”
请注意labdas已经变化为lambdas
UnaryOperator headerProcessing =
(String text) -> "From Raoul, Mario and Alan: " + text;
UnaryOperator spellCheckerProcessing =
(String text) -> text.replaceAll("labda", "lambda");
Function pipeline =
headerProcessing.andThen(spellCheckerProcessing);
String result = pipeline.apply("Aren't labdas really sexy?!!");
5) 工厂模式
使用工厂模式,你无需向客户暴露实例化的逻辑就能完成对象的创建。
比如,我们假定你为 一家银行工作,他们需要一种方式创建不同的金融产品:贷款、期权、股票,等等。 通常,你会创建一个工厂类,它包含一个负责实现不同对象的方法,如下所示:
public class ProductFactory {
public static Product createProduct(String name){
switch(name){
case "loan": return new Loan();
case "stock": return new Stock();
case "bond": return new Bond();
default: throw new RuntimeException("No such product " + name);
}
}
}
Product p = ProductFactory.createProduct("loan");
引用贷款 (Loan)构造函数的示例:
Supplier loanSupplier = Loan::new;
Loan loan = loanSupplier.get();
所以通过这种方式,就可以将最上面的代码重构,创建一个Map,将产品名映射到对应的构造函数:
final static Map> map = new HashMap<>();
static {
map.put("loan", Loan::new);
map.put("stock", Stock::new);
map.put("bond", Bond::new);
}
像之前使用工厂设计模式那样,利用这个Map来实例化不同的产品:
public static Product createProduct(String name){
Supplier p = map.get(name);
if(p != null) return p.get();
throw new IllegalArgumentException("No such product " + name);
}
为了完成这个任务,你需要创建一个特殊的函数接口TriFunction。最终 的结果是Map变得更加复杂。
public interface TriFunction{
R apply(T t, U u, V v);
}
Map> map
= new HashMap<>();
peek的设计初衷就是在流的每个元素恢复运行之 前,插入执行一个动作。但是它不像forEach那样恢复整个流的运行,而是在一个元素上完成操 作之后,它只会将操作顺承到流水线中的下一个操作。
List result =
numbers.stream()
.peek(x -> System.out.println("from stream: " + x))
.map(x -> x + 17)
.peek(x -> System.out.println("after map: " + x))
.filter(x -> x % 2 == 0)
.peek(x -> System.out.println("after filter: " + x))
.limit(3)
.peek(x -> System.out.println("after limit: " + x))
.collect(toList());
防止出现NullPointerException
public class Person {
private Car car;
public Car getCar() { return car; }
}
public class Car {
private Insurance insurance;
public Insurance getInsurance() { return insurance; }
}
public class Insurance {
private String name;
public String getName() { return name; }
}
调用
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
这个时候,如果一个人没有车呢,那么在get车保险的时候指定会报出NullPointerException。
很多时候可能会这么做:
public String getCarInsuranceName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "Unknown";
}
但真的不够友好
public class Person {
private Optional car;
public Optional getCar() { return car; }
}
public class Car {
private Optional insurance;
public Optional getInsurance() { return insurance; }
}
public class Insurance {
private String name;
public String getName() { return name; }
}
1) 创建Optional 对象
就以上改动后的代码,进行操作
a.声明一个空的Optional- empty()
Optional optCar = Optional.empty();
b.依据一个非空值创建Optional- of()
Car car = new Car();
Optional optCar = Optional.of(car);
这时候如果car是空,就会直接抛出NullPointerException
c.可接受null的Optional- ofNullable()
Optional optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional对象就是个空对象。
2) 使用 map 从 Optional 对象中提取和转换值
比如,你可能想要从insurance公司对象中提取 公司的名称。
提取名称之前,你需要检查insurance对象是否为null,代码如下:
String name = null;
if(insurance != null){
name = insurance.getName();
}
Optional optInsurance = Optional.ofNullable(insurance);
Optional name = optInsurance.map(Insurance::getName);
如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如 果Optional为空,就什么也不做。
3) 使用 flatMap 链接 Optional 对象
a.使用Optional获取car的保险公司名称
public String getCarInsuranceName(Optional person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
b.使用Optional解引用串接的Person/Car/Insurance对象
4) 默认行为及解引用 Optional 对象
Optional类提供了多种方法读取 Optional实例中的变量值。
5) 两个 Optional 对象的组合
假设你有这样一个方法,它接受一个Person和一个Car对象,并以此为条件对外 部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:
public Insurance findCheapestInsurance(Person person, Car car) {
// 不同的保险公司提供的查询服务
// 对比所有数据
return cheapestCompany;
}
还假设你想要该方法的一个null-安全的版本,它接受两个Optional对象作为参数, 返回值是一个Optional对象,如果传入的任何一个参数值为空,它的返回值亦为空.
public Optional nullSafeFindCheapestInsurance(
Optional person, Optional car) {
if (person.isPresent() && car.isPresent()) {
return Optional.of(findCheapestInsurance(person.get(), car.get()));
} else {
return Optional.empty();
}
}
这么写也不是不可以,但是如果真的这么写的话,和之前大量的!=null又有什么区别呢
所以要做改进,把flatMap用到实处
以不解包的方式组合两个Optional对象
public Optional nullSafeFindCheapestInsurance(
Optional person, Optional car) {
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}
6) 使用 filter 剔除特定的值
比如,你可能需要检查保险公司的名 称是否为“Cambridge-Insurance”。为了以一种安全的方式进行这些操作,你首先需要确定引用指 向的Insurance对象是否为null,之后再调用它的getName方法,
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
Optional optInsurance = ...;
optInsurance.filter(insurance ->
"CambridgeInsurance".equals(insurance.getName()))
.ifPresent(x -> System.out.println("ok"));
1) 用 Optional 封装可能为 null 的值
假设你有一个Map方法,访问由key索引的值时,如果map 中没有与key关联的值,该次调用就会返回一个null。
Object value = map.get("key");
这个时候如果get为空就会报错,可以采用Optional.ofNullable方法:
Optional
2) 异常与 Optional 的对比
使用静态方法Integer.parseInt(String),将 String转换为int。在这个例子中,如果String无法解析到对应的整型,该方法就抛出一个 NumberFormatException。
将String转换为Integer,并返回一个Optional对象
public static Optional stringToInt(String s) {
try {
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
1) 使用LocalDate和LocalTime
LocalDate date = LocalDate.of(2014, 3, 18);
//2014-03-18
int year = date.getYear();
//2014
Month month = date.getMonth();
//MARCH
int day = date.getDayOfMonth();
//18
DayOfWeek dow = date.getDayOfWeek();
//TUESDAY
int len = date.lengthOfMonth();
//31.查看一个月一共多少天
boolean leap = date.isLeapYear();
//false(不是闰年)
LocalDate today = LocalDate.now();
//获取当前时间
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
LocalTime time = LocalTime.of(13, 45, 20);
//13:45:20
int hour = time.getHour();
//13
int minute = time.getMinute();
//45
int second = time.getSecond();
//20
LocalDate date = LocalDate.parse("2014-03-18");
LocalTime time = LocalTime.parse("13:45:20");
2) 合并日期和时间-LocalDateTime
// 2014-03-18T13:45:20
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
3) 机器的日期和时间格式 -Instant
通过向静态工厂方法
ofEpochSecond
传递一个代表秒数的值创建一个该类的实例。静 态工厂方法ofEpochSecond
还有一个增强的重载版本,它接收第二个以纳秒为单位的参数值,对 传入作为秒数的参数进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在0到999 999 999
之间。
//3秒
Instant.ofEpochSecond(3);
//3秒+0纳秒
Instant.ofEpochSecond(3, 0);
//2秒+100万纳秒,即1秒
Instant.ofEpochSecond(2, 1_000_000_000);
//4秒-100万纳秒
Instant.ofEpochSecond(4, -1_000_000_000);
//可以通过Instant.now()返回int型时间戳
int day = Instant.now();
4) 定义Duration或Period-持续时间或期间
上面的所有类都实现了Temporal接口,这个接口定义了如何读取和操纵为时间建模和对象的值
可以创建两个LocalTimes对象、两个LocalDateTimes对象,或者两个Instant对象之间的duration
Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);
Period tenDays = Period.between(LocalDate.of(2014, 3, 8),
LocalDate.of(2014, 3, 18));
Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
ChronoUnit很重要,请记住这个枚举类
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.withYear(2011);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
1) 使用TemporalAdjuster- 下周、下个工作日等
import static java.time.temporal.TemporalAdjusters.*;
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
LocalDate date3 = date2.with(lastDayOfMonth());
TemporalAdjuster类中的工厂方法
2) 实现一个定制的TemporalAdjuster
设计一个NextWorkingDay类,该类实现了TemporalAdjuster接口,能够计算明天 的日期,同时过滤掉周六和周日这些节假日。
如果当天的星期介于周一至周五之间,日期向后移动一天;如果当天是周六或者周日,则 返回下一个周一。
NextWorkingDay类的实现如下:
public class NextWorkingDay implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
}
}
date = date.with(temporal -> {
DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(
temporal -> {
DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
//使用
date = date.with(nextWorkingDay);
3) 打印输出及解析日期-时间对象(格式化)
新的 java.time.format包就是特别为这个目的而设计的。这个包中,最重要的类是DateTimeFormatter。
所有的 DateTimeFormatter实例都能用于以一定的格式创建代表特定日期或时间的字符串。比如,下 面的这个例子中,我们使用了两个不同的格式器生成了字符串:
LocalDate date = LocalDate.of(2014, 3, 18);
//20140318
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
//2014-03-18
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date1 = LocalDate.parse("20140318",DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18",DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date.format(italianFormatter); // 18. marzo 2014
LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
ZoneId zoneId = TimeZone.getDefault().toZoneId();
1) 为时间点添加时区信息
//日期
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
//日期加时间
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
//时间戳
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);
//通过ZoneId,你还可以将LocalDateTime转换为Instant:
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
Instant instantFromDateTime = dateTime.toInstant(romeZone);
//你也可以通过反向的方式得到LocalDateTime对象:
Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
Q.E.D.