JAVA8

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的入参。


image.png

3 流

流可以看做是对源的一个查询。
  流只会在需要时才会计算值,并不是一开始把所有内容都存放。
  对流进行串联进行多次遍历操作时,会对循环进行合并,只进行一次循环。
  流式处理,如果某个操作返回的不再是流,我们成为终端操作,如 count()。
  流使用一般包括三件事:数据源、中间操作链、终端操作。

4 使用流

4.1 筛选、切片

filter 过滤元素
    distinct 去重
    limit 截取
    skip   跳过多少个元素

4.2 映射

map 每一个元素进行一次映射,形成一个新的流
    flatMap 一言以蔽之, flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。


image.png

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 累计函数
image.png

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); //归并累加
}

上面程序的操作过程如下:


image.png

转换成并行流之后,流其实没有任何变化,只是内部设置了一个标记为,标记为并行处理而已。

(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高的多。

以下是对一些数据结构的分析:


image.png

9.1.4 分支/合并框架
  //TODO

9.1.5 Spliterator
  //TODO

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352