Java8实战总结

680 阅读9分钟

[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,它接受一个参数值,并返回truefalse.它避免了把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对象
流的无状态和有状态
mapfilter等操作会从输入流中获取每一个元素,并在输出流中得到01个结果,这些操作都是无状态的:他们没有内部状态.
但是sort、distinct等操作一开始都和filtermap差不多->都是接受流、在生成一个流,关键的区别是,排序需要历史的数据的状态,所以需要一个缓冲区存放,这就是有状态的操作.

原始类型流特化

为了避免冗余的拆装箱,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等对象进行替换