[toc]
1.为什么关心Java8
行为参数化
可以把行为参数化这样抽象的概念,理解为将方法或者一段代码当作方法的参数。
方法不仅仅是基本、复合类型还可以是一段代码。
Java8使用lambda表达式实现这一操作,也取缔繁冗的匿名类方法.
流处理
流是一系列数据项,一次只生成一项。程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据写入输出流。
因为流本质上是让数据变成并行操作,所以使用stream必须没办法直接操作共享变量。
java中的函数
函数一般指方法,尤其是静态方法;Java8中新增了函数--值的一种新形式
方法和Lambda作为一等公民
Java8的第一个功能方法引用,例如实现一个方法筛选隐藏文件.
File[] hiddenFiles = new File(".").listFiles(new FileFilter(){
public boolean accept(File file){
return file.isHidden();
}
})
通过使用Lambda可以简化代码为
File[] hiddenFiles = new File(".").listFiles(File::isHidden)
因为已经有了函数isHidden,因此只需要Java8的函数引用::语法(把这个方法作为值)将其传给listFiles方法.我们可以清晰的感受,这个方法的参数可以方法,而不仅仅是基本or复合类型
Java8中可以使用::作为方法的引用,将方法的作为参数.
Lambda---匿名函数
(int x) -> x + 1,表示"调用时给定参数x,返回x+1值的函数"
什么是谓词
Predicate,它接受一个参数值,并返回true或false.它避免了把boolean封装在boolean里面。
从传递方法到Lambda
把方法作为值来传递显然很有用,但是有的时候如果方法很短的话就很烦人。可以通过匿名函数或者lambda解决问题。
filterApples(inventory,(Apple a) -> "green".equals(a.getColor()));
匿名类
匿名类和java的局部类差不多,但它没有名字。它允许同时声明并实例化一个类。它允许随便创建。
它往往很笨重,一般会出现很多行,使用Lambda表达式可以优雅的替换冗余的匿名类.
如:
List<Apple> result = filterApples(iventory,(Apple apple) -> "red".equals(apple.getColor()));
常用场景:
1.Comparator排序
inventory.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWegiht());
2.Runnable执行代码块
Thread t = new Thread(() -> System.out.println("Hello,Wolrd"));
2. Lambda表达式
把它当作匿名功能,可以把它理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
- 匿名->我们说匿名,因为它不像普通的方法那样有一个明确的名称:写的少而想的多
- 函数->我们说它是函数,是因为Lambda函数不像方法哪种属于某个特定的类。但它还具备和方法一致的特征
- 传递->它可以作为参数传递给方法或存储在变量中
- 简洁->代码简单、大方
inventory.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWegiht());
()->a1、a2就属于参数列表,->把参数列表与lambda主体分隔开,lambda主体->compareTo就是Lambda的返回值.
例子:
- 布尔表达式 (List<String> list) -> list.isEmpty();
- 创建对象 () -> new Apple(10);
- 消费一个对象 (Apple a) -> {
System.out.print();
}
- 从一个对象选择/抽取 (String s) -> s.length();
- 组合两个值 (int a, int b) -> a * b;
- 比较两个对象 如compareTo
在哪里使用Lambda
在哪里使用lbd?**可以在函数式接口使用**
- 函数式接口,就是只定一个抽象方法的接口。如Comparator<T>,Runnable,Callable等
@FunctionInterface就是代表等中着函数接口
**Function**
Function接口定义了一个apply的方法,它接受一个泛性T的对象,并返回一个泛型R的对象。如果需要定一个Lambda
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = Lists.newArrayList();
list.forEach(element -> result.add(f.apply(element)));
return result;
}
public static void main(String[] args) {
List<Integer> l = map(Arrays.asList("lambda","in","action"), String::length);
System.out.println(l);
}
function的apply方法可以做到,输入T,返回R.
**Consumer**
它接受一个T类型的参数,没有返回值。这么理解,如果一块代码,只有有两种情况,AB两种情况本质上只是对bean的值进行简单的更替,就可以把这个bean设置为consumer这样。
**Lambda函数式接口例子**
布尔表示式 (List<String> list) -> list.isEmpty(); Predicate<List<String>>
创建对象 () -> new Apple(10) Supplier<Apple>
消费一个对象 (Apple a) -> Sysout(a.getWeight()) Consumer<Apple>
从一个对象中选择/提取 (String s) -> s.length() Function<String,Integer>
合并两个值 (int a,int b) -> a * b IntBinaryOperator
比较两个对象 (Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) Comparetor<Apple>
**小结**
函数式接口就是仅仅声明一个抽象方法的接口
为了避免装箱操作,可以使用IntPredicate
环绕执行模式(即在方法所需要的代码中间,你需要执行点儿操作。核心是行为参数化)
3.引入流&使用流
流只能遍历一次,流是内部迭代。迭代器是外部迭代,stream是外部迭代。内部迭代取出每一个项目加以处理,外部迭代可以在迭代过程中做选择,内部迭代可以透明地并行处理,如果外部迭代需要我们自己管理所有的并行问题。
总而言之,流的使用一般包括三件事:
一个数据源来执行一个查询;
一个中间操作链,形成一条流的流水线(filter、sorted)
一个终端操作,执行流水线,并行产生结果(System::out:println)
flatMap的作用是,把每个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
查找和匹配
查看数据集中的某些元素是否匹配一个给定的属性,stream_api通过allMatch、anyMatch、noneMatch、findFirst、findAny提供了这样的工具.
anyMatch方法作用是流中是否有一个元素能匹配给定的谓词,它返回boolean,它是一个终端操作。
检查谓词是否匹配所有的元素,allMatch
noneMatch就是没有任何元素与给定的谓词匹配
这三个操作都用到了我们所谓的短路,也就是&&_||
查找元素
findAny方法将返回当前流中的任何元素。它可以与其他操作结合使用.
Optional简介
Optional<T>是一个容器类,代表一个值存在或不存在
findFirst有些流有一些出现顺序,来指定流中项目出现的逻辑顺序。对于这种流,你可能想找到第一个元素。为此有一个findFirst方法。
findFirst和findAny,如果对顺序不关心,使用findAny,因为findFirst会对并行流限制更多.
归约
reduce操作表达更复杂的查询,reduce接受两个参数( 一个初始值,一个binaryOperator<T>)
reduce还有一个重载的变体,它不接受初始值,它会返回一个Optional对象
流的无状态和有状态
map、filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果,这些操作都是无状态的:他们没有内部状态.
但是sort、distinct等操作一开始都和filter和map差不多->都是接受流、在生成一个流,关键的区别是,排序需要历史的数据的状态,所以需要一个缓冲区存放,这就是有状态的操作.
原始类型流特化
为了避免冗余的拆装箱,stream提供mapToInt等方法.
IntStream的range和rangeClosed可以表示流数据的范围
由文件生成流:
java中用于处理文件等io操作的nio api,可以利用stream api的很多静态方法都会返回一个流。如Files.lines
4.用流收集数据
预定义收集器
将流元素约和汇总为一个值
元素分组
- collectors.groupingBy
Map<Integer, List<Dish>> collect = dishList.stream().collect(Collectors.groupingBy(Dish::getCategory));
元素分区
- collectors.partitioningBy返回一个key为true或者false的map
5.并行流
并行流就是把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。并行流内部使用的是forkJoinPool,并行流不要做多线程进行数据竞争,否则就完全没有意义.
> 高效使用并行流
1.避免装箱、拆箱的操作
2.依赖顺序的操作很不适合并行流,如limit和FindFirst
3.较小的数据量也不是什么好的场景,因为并行本身需要额外的开销
4.要考虑并行流的数据是否容易拆解。例如ArrayList的拆分效率比LinkedList高的多.前者不需要遍历就可以平均拆分,后者需要遍历拆分
Spliterator
它是可分迭代器,它是为了并行设计的迭代器
6.默认方法
> Java8允许在接口内声明静态方法
向接口添加防范是诸多问题的罪恶之源;一旦接口发生变化,实现这些接口的类往往需要更新,提供新添方法的实现才能适配接口的变化。
它可以让类自动继承接口的一个默认实现
> 抽象类和抽象接口
一个类只能继承一个抽象类,但是一个类可以实现多个接口。
其次,一个抽象类可以通过实例变量保存一个通用状态,而接口不能有instance
> 多继承的问题
通过实现多个接口可以实现接口默认方法的多继承
> 解决冲突的规则
1.类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级
2.如果无法根据第一条条件判断,那么子接口的优先级更高。优先选择最具体实现的默认方法的接口,既如果B继承A,那么B就比A更具体
3.最后,如果无法判断,继承多接口的类必须通过显示覆盖和调用期望的方法。
B因为继承自A,所以B比A更具体,所以选择使用B的方法
> 如果实现的两个优先级接口相同的接口,那么无法无法通过编译.
针对图上的菱形继承问题,也必须通过显示调用父类的方法进行使用。
7.Optional
8.使用LocalDate、LocakDateTime等api替换Date
Date本身线程不安全,而且本身格式也很丑.使用LocalDate等对象进行替换