1 行为参数化
package com.gotten.studyjava8.actionparam;
import java.util.ArrayList;
import java.util.List;
public class CollectionFilter {
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for(T t : list) {
if(predicate.test(t)) {
result.add(t);
}
}
return result;
}
}
package com.gotten.studyjava8.actionparam;
import java.util.Arrays;
import java.util.List;
public interface Predicate<T> {
boolean test(T t);
}
package com.gotten.studyjava8.actionparam;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Apple {
private String color;
private int weight;
}
package com.gotten.studyjava8.actionparam;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Apple> list = Arrays.asList(new Apple("红色", 10), new Apple("红色", 20));
List<Apple> filter = CollectionFilter.filter(list, (Apple a) -> a.getWeight() > 10);
System.out.println(filter);
}
}
2 lambda表达式
2.1 lamda表达式可以使用在哪
lambda表达式可以用在函数式接口的地方,函数式接口指只有一个抽象方法的接口。
@FunctionalInterface定义在一个函数式接口上,如果接口不是函数式接口使用这个注解,将会编译时报错,但是这个注解不是必须的。
2.2 lamda表达式语法
完整语法如下:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
例子如下:
list = Arrays.asList(1L,2L,8L,2L,5L);
Collections.sort(list, (Long o1, Long o2)->{int i = o1 > o2 ? 0 : 1; return i;});
2.2.1 简略写法
某些情况下lambda表达式可以省略某些部分。
(1)变量的类型可以省略
list = Arrays.asList(1L,2L,8L,2L,5L);
Collections.sort(list, (o1, o2)->{int i = o1 > o2 ? 0 : 1; return i;});
System.out.println(Arrays.toString(list.toArray()));
(2)单个参数可以省略()
sList = Arrays.asList("aaa", "bbb", "ccc", "ddd");
sList.stream().map((str)->{return str.toUpperCase();}).forEach(str->System.out.println(str));
(3)单挑语句可以省略{},并且省略return
sList = Arrays.asList("aaa", "bbb", "ccc", "ddd");
sList.stream().map((str)->str.toUpperCase()).forEach(str->System.out.println(str));
某些情况下,省略会报错,还未找到规律,反正如果报错,就按照完全的语法编写就好。
2.2.2 异常
如果lambda表达式需要抛出异常,对应的函数式接口的抽象方法,必须声明抛出异常。
2.3 常用函数式接口
Predicate<T>: 断言,只有一个test抽象方法,接收一个泛型参数,返回boolean;
boolean test(T t);
Consumer<T>: 消费,只有一个accept方法,接收一个泛型,返回void;
void accept(T t);
Function<T, R>: 函数,只有一个apply方法,返回一个泛型R,接收一个泛型T;
R apply(T t);
Comparator<T>: 比较器,int compare(T o1, T o2);
int compare(T o1, T o2);
还有专门针对装箱的函数式接口(提升性能,因为引用类型放在堆,占用堆空间),如IntPredicate等,具体看java.util.function包下面实现。
2.4 方法引用和构造器引用
2.4.1 方法引用定义
方法引用是作为lambda表达式的一种快捷写法,其本质是lambda表达式。
如: String::toLowerCase其实就是 String -> s.toLowerCase()
2.4.2方法引用的分类
objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod
前两种方式类似,等同于把lambda表达式的参数直接当成instanceMethod|staticMethod的参数来调用。比如System.out::println等同于x->System.out.println(x);Math::max等同于(x, y)->Math.max(x,y)。
最后一种方式,等同于把lambda表达式的第一个参数当成instanceMethod的目标对象,其他剩余参数当成该方法的参数。比如String::toLowerCase等同于x->x.toLowerCase()。
可以这么理解,前两种是将传入对象当参数执行方法,后一种是调用传入对象的方法。
sList = Arrays.asList("aaa", "bbb", "ccc", "ddd");
sList.stream().map(String::toUpperCase).forEach(System.out::println);
sList.stream().map(new LambdaDemo()::up).forEach(System.out::println);
sList.stream().map(LambdaDemo::up2).forEach(System.out::println);
public static String up2(String s) {
return s.toUpperCase();
}
2.4.3构造器引用
构造器引用同样是lambda的快捷写法,同样可以适用有参和无惨的构造方法,这个和目标类型(函数式接口)有关。
如:Function<String, BigDecimal>对应的lambda类型是 BigDecimal -> String,所以可以试用BigDecimal::new,new的构造方法入参是String,创建完返回是BigDecimal。
sList = Arrays.asList("1.12345");
sList.stream().map(BigDecimal::new).map(BigDecimal::toString).forEach(System.out::println);
2.5 复合lambda表达式
复合是指连续调用多个lambda表达式,进行多次操作,类似linux的管道。原理就是每次操作后都返回一个相同函数接口的实例,无限的复合处理下去。
如inventory.sort(comparing(Apple::getWeight).reversed()),comparing(Apple::getWeight)返回的是一个Comparator的实现,.reversed()是Comparator的一个缺省实现方法,实现翻转。
2.5.1 比较器的复合(Comparator)
reversed:逆序
thenComparing:比较器链
2.5.2 断言复合(Predicate)
negate:非
and:与
or:或
2.5.3 函数复合(Function)
andThen:把第一个Function执行的结果,当做是第二个Function的入参。
3 流
流可以看做是对源的一个查询。
流只会在需要时才会计算值,并不是一开始把所有内容都存放。
对流进行串联进行多次遍历操作时,会对循环进行合并,只进行一次循环。
流式处理,如果某个操作返回的不再是流,我们成为终端操作,如 count()。
流使用一般包括三件事:数据源、中间操作链、终端操作。
4 使用流
4.1 筛选、切片
filter 过滤元素
distinct 去重
limit 截取
skip 跳过多少个元素
4.2 映射
map 每一个元素进行一次映射,形成一个新的流
flatMap 一言以蔽之, flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
4.3 查找、匹配
anyMatch 部分符合
allMatch 全部符合
noneMatch 没有符合
findAny 找到马上返回,返回Optional,没有Optional为空
findFirst 找到第一个,串行流里面和 findAny是一致的,并行流时存在差异
4.4 规约
reduce: 累计处理(做累加之类的操作)
count() 计数
BigDecimal totalBal = ccsLoans.stream().map(loan -> loan.getLoanBalTotal()).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
5 数据流
IntStream、 DoubleStream、LongStream分别将流中的元素特化为int、 long和double,从而避免了暗含的装箱成本。
sum()
max
min
average
boxed() 转换回对象流
OptionalInt 获取max、min时,因为没有元素会返回OptionalInt,避免返回默认值出现错误。
int max = maxCalories.orElse(1);
IntStream.rangeClosed(1, 100) 获取数值范围的流
7.构建流
(1)由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
(2)数组创建流
int sum = Arrays.stream(numbers).sum();
(3)文件生成流
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
(4)函数生成流:无限流
- 迭代
Stream.iterate(0, n -> n + 2) 由0开始,依次+2
- 生成
Stream.generate(Math::random) 随便生成
8 用流来收集数据
8.1 收集器
stream().collect(Collector<T, A, R> collector)
以下所有的简写方法,都是Collectors的静态导入方法。
8.2 规约和汇总
(1)最大值和最小值
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator));
(2)汇总
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
summingLong 求和
summingDouble
averagingInt 平均
summarizingInt 汇总
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
IntSummaryStatistics{count=9, sum=4300, min=120,
average=477.777778, max=800}
(3)连接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().collect(joining()); //非string对象,会自动调用toString方法进行拼接
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", ")); //拼接时,可以指定分隔符
(4)广义规约汇总
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j)); //0是初始值,转换函数,BinaryOperator 累计函数
8.3 分组
8.3.1 简单分组
使用Collectors.groupingBy收集器进行分组。
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));
public enum CaloricLevel { DIET, NORMAL, FAT }
Map<CaloricLevel, List<Dish>> 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;
} ));
8.3.2 多级分组
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
})
)
);
8.3.3 按子组收集数据
某些情况下,需要先分组,并且对分组后的数据进行收集。
Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));
普通函数groupingBy(f)是groupingBy(f,toList())的缩写。
分组后的对分组再次进行处理,可以使用collectingAndThen处理:
Map<Dish.Type, Dish> mostCaloricByType = menu.stream().collect(groupingBy(Dish::getType, collectingAndThen(maxBy(comparingInt(Dish::getCalories)),Optional::get)));
8.4 分区
分区是分组的一种特殊情况,分区时,作为分组的key为true或者false。
Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));
partitioningBy接收2个参数,一个Predicate,还有一个Collector(Collector缺省为toList)。
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
8.5 收集器接口
//TODO
8.6 开发自己的收集器
//TODO
9 并行数据处理与性能
9.1 并行流
9.1.1 顺序流与并行流之间转换
(1)顺序流转换成并行流:
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1) //构建一个自然数的顺序流
.limit(n) //获取前面n个数
.parallel() //转换成并行流
.reduce(0L, Long::sum); //归并累加
}
上面程序的操作过程如下:
转换成并行流之后,流其实没有任何变化,只是内部设置了一个标记为,标记为并行处理而已。
(2)并行流转换成顺序流:
stream.parallel()
.filter(...)
.sequential() //转换成串行流
.map(...)
.reduce();
每一次的流失处理,对于每个元素,所有步骤都是串行执行,并不是所有元素都执行完第一步再执行第二步。所有,如果一次处理中,多次在并行流和串行流之间来回转换,最后一定指定的流类型,决定这次处理到底是使用串行流还是并行流。
(3)并行流的线程池
并行流内部默认使用了ForkJoinPool实现多线程执行,默认线程池的数量就是CPU的核数。
可通过改变系统参数,修改默认线程池的大小:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
9.1.2 并行处理的性能
在对数据进行累加的示例中,并行流的性能会比顺序流更差。因为Stream.iterate产生的每一个元素,都是依赖上一个元素的,硬把它拆到多个流处理,其实也是要等待,更加复杂。
所以,如果并行流使用不当,反而带来更差的性能。
如果换成以下实现,将会带来更好的性能。因为rangeClosed是范围操作,很容易拆成多段并行处理。由此可知,并行处理合适的数据结构,非常重要。
public static long rangedSum(long n) {
return LongStream.rangeClosed(1, n)
.reduce(0L, Long::sum);
}
9.1.3 正确使用并行流
并行流使用时,并没有银弹,但是有一些可以遵循的规则:
(1)如果有疑问,直接测量:不要相信多于多少个元素使用并行流的说法,不同的数据结构,不同的机器,差异非常大。
(2)留意装箱:装箱拆箱对性能影响很大。
(3)某些操作,并行流比顺序流性能更差:如limit、findFirst等,因为这些操作依赖了顺序性。
(4)估算流水线操作的总成本:设N是要处理的元素的总数, Q是一个元素通过流水线的大致处理成本,则N*Q就是这个对成本的一个粗略的定性估计。 Q值较高就意味着使用并行流时性能好的可能性比较大。
(5)数据量不多,不考虑并行流:花销在切割上的性能损耗更多。
(6)考虑数据结构是否易于拆解:如ArrayList的拆分效率比LinkedList高的多。
以下是对一些数据结构的分析:
9.1.4 分支/合并框架
//TODO
9.1.5 Spliterator
//TODO