Java8 新特性

1. lambda 表达式

1.1 lambda 的基本语法

左边:对应接口中的抽象方法的参数
右边,对应接口中的抽象方法的实现,即所需要执行的功能,也即 lambda 体

注:在 jdk1.7 及以前,在 lambda 表达式内部要调用类的成员变量的时候,这个成员变量必须用 final 显示声明,而 jdk1.8 之后,不需要显示声明,系统会默认我们调用的这个成员变量是 final 的,但是你对这个变量进行修改的时候仍然是会报错的

lambda 的语法形式:
① 无参数,无返回值:()-> 具体实现
② 有一个参数,无返回值:(x)-> 具体实现
③ 若只有一个参数,小括号可以省略不写:x -> 具体实现
④ 若有多个参数,多条语句,加个大括号就行了:(x,y)-> { 具体实现 }
⑤ 若 lambda 体重只有一条语句,return 和大括号都可以省略不写,如Comparator<Integer>com = (x,y) -> Integer.compare(x,y)。原本应该是

Comparator<Integer>com = (x,y) -> {
	return Integer.compare(x,y);
}

⑥ lambda 表达式的参数列表的数据类型可以省略不写,因为 JVM 编译器通过上下文可以推断出数据类型,即“类型推断”

注:lambda 需要“函数式接口”的支持
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用 @FunctionalInterface 修饰

1.2 lambda 四大内置函数式接口

类型语法
Consumer:消费型接口void accept(T t)
Supplier :供给型接口T get()
Function<T,R>: 函数型接口R apply(T t)
Predicate: 断言型接口boolean test(T t)

1. Consumer 消费型接口:

    public  void happy(double m, Consumer<Double>con){
        con.accept(m);
    }
    @Test
    public void test01(){
        happy(1000,(m) -> System.out.println("消费了"+m+"元"));
    }

2. Supplier 供给型接口:

 @Test
    public void test02(){
        String s=supplier(()->"生产一辆车");
        System.out.println(s);
    }
    
    public String supplier(Supplier<String>sup){
        return sup.get();
    }

3.函数型接口

 @Test
    public void test03(){
        System.out.println(strHandler("abcd",(s)->s.toUpperCase()));
    }

    public String strHandler(String s, Function<String,String>fun){
        return fun.apply(s);
    }

4.断言型接口

@Test
    public void test04(){
        Integer arr[]={1,2,3,4,5};
        for (Integer a : arr) {
            Pre(a,(b)->{
                if(b<=3) return true;
                return false;
            });
        }
    }
    public void Pre(int a,Predicate<Integer>pre){
        System.out.println(a+" "+pre.test(a));
    }

2. 方法引用与构造器引用

2.1 方法引用

方法引用:若 lambda体 中的内容有方法已经实现了,我们可以使用 “方法引用” (可以理解为方法引用是 lambda 表达式的另外一种表现形式)

主要有三种语法格式:
对象 :: 实例方法名
类 :: 静态方法名
类 :: 实例方法名

案例演示:
对象 :: 实例方法名

@Test
    public void test05(){
        // 正常的用 lambda 表达式表示:传入一个数并打印应该是这样写的
        Consumer<String>con=(x)-> System.out.println(x);
        con.accept("小猪猪1号");
        /* 
        由于这里的 System.out.println() 函数和Consumer接口的
        参数列表和返回值类型都一致,所以我们可以把上面的lambda表达式
        改写成方法引用的形式,如下:
         */
        Consumer<String>con1= System.out :: println;// 对象 :: 实例方法名
        con1.accept("小猪猪2号");
    }

注意:
① lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
② 若 lambda 参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 ClassName :: method

2.2 构造器引用

语法格式:ClassName :: new
案例演示:

    @Test
    public void test06(){
        Supplier<Employee> sup=() -> new Employee();
        //构造器引用
        Supplier<Empployee>suo2=Employee:new;
    }

2.3 数组引用

语法格式:Type[] :: new

    @Test
    public void test06(){
        Function<Integer,String[]>fun = (x) -> new String[x];

        Function<Integer,String[]>fun1 = String[] :: new;
    }

3. Stream 流式计算

Stream流:和普通的流一样,就是对数据进行操作,是数据就会有数据源(集合、数组等),而流对数据的操作其实主要表现在对数据的传输
但是和普通的流不一样的是,在传输的过程中可以对数据源进行一系列流水线式的中间操作,最终产生一个新流(原来的数据源并不改变,怎么理解呢,就是相当于复制文件,原来的文件并不会改变)。
在这里插入图片描述
流(Stream)到底是什么? 是数据渠道,用于操作数据源(集合、数组)所生成的元素序列。“集合讲的是数据,流讲的是运算!”

注意:
① Stream 自己不会存储元素
② Stream 不会改变源对象。相反,他们会返回一个持有结果的新的 Stream
③ Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

3.1 创建流的几种方式

1.Java8 中的 Collection 接口被扩展,提供了两个获取流的方法,使用这个方法,包括继承Collection的接口,如:Set,List,Map,SortedSet 等等:

  • default Stream<E> stream(); 返回一个顺序流
  • default Stream<E> parallelStream(); 返回一个并行流

2.Arrays.stream,我们可以通过Arrays的静态方法,传入一个泛型数组,创建一个流

3.Stream.of,我们可以通过Stream的静态方法,传入一个泛型数组,或者多个参数,创建一个流,这个静态方法,也是调用了Arrays的stream静态方法,如下

4.Stream.iterate,是Stream接口下的一个静态方法,从名字也可以看出,这个静态方法,是以迭代器的形式,创建一个数据流

5.Stream.generate,也是stream中的一个静态方法

案例演示:

    @Test
    public void test(){
        //1.通过 Collection 系列集合提供的 stream() 或 parralleStream()
        List<String> list=new ArrayList<>();
        Stream<String> stream1=list.stream();
        
        //2. 通过 Arrays 中的静态方法 stream 获得数组流
        Integer []arr=new Integer[10];
        Stream<Integer>stream2= Arrays.stream(arr);

        //3. 通过 Stream 中的静态方法 of()
        Stream<String>stream3=Stream.of("aa","bb","cc");

        //4. 创建无限流
        //迭代
        Stream<Integer>stream4=Stream.iterate(0,(x)->x+2);
        
        //生成
        Stream.generate(()->Math.random()).limit(5);
    }

3.2 Stream_筛选与切片

  • filter – 接收 lambda,从流中排除某些元素
  • limit – 截断流,使其元素不超过给定数量
  • skip(n) – 跳过元素,返回第一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流,与 limit(n) 互补
  • distinct – 筛选,通过流所生成元素的 hashcode() 和 equals() 去除重复元素

案例演示:

 @Test
    @Test
    public void test(){
        Integer []arr={1,2,3,4,4,4,4,5,6,7,8,9};

        // 中间操作:不会执行任何操作
        Stream<Integer>stream = Arrays.stream(arr).filter((a)->{
            System.out.println("Stream 的中间操作");
            return a>=3;
        }).skip(2).distinct().limit(1);

        //终止操作:一次性执行全部内容,即“惰性求值”
        stream.forEach(System.out::println);
    }

注意:
filter 是一个短路运算,就比如 limit(2) ,已经取到满足条件的两条数据之后, filter 就不会再继续向下执行了

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为 “惰性求值”

3.3 Stream_映射

  • map – 接收 lambda ,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap – 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连成一个流

stream中的flatmap是stream的一种中间操作,它和stream的map一样,是一种收集类型的stream中间操作,但是与map不同的是,它可以对stream流中单个元素再进行拆分(切片),从另一种角度上说,使用了它,就是使用了双重for循环

案例演示:

  // map -- 将list里面的小写字母全部转成大写
    @Test
    public void test1() {
        List<String> list = Arrays.asList("aaa", "bbb", "ccc");
        Stream<String> stream = list.stream();
        stream.map((s) -> s.toUpperCase()).forEach(System.out::println);
    }
//flatMap -- 将多个 List 合并成一个
    @Test
    public  void test2(){
        List<String> list1= new ArrayList(){{add("1");add("2");}};
        List<String> list2= new ArrayList(){{add("3");add("4");}};
        List<String> list3= new ArrayList(){{add("5");add("6");}};
        List<List<String>> list = new ArrayList(){{add(list1);add(list2);add(list3);}};
        System.out.println(list.stream().flatMap(List::stream).collect(Collectors.toList()));
    }

3.4 stream_排序

sorted() – 自然排序(Comparable)
sorted(Comparable com) – 定制排序(Comparator)

    @Test
    public void test() {
        List<Emp>list=Arrays.asList(new Emp("zs",10),new Emp("ls",15),new Emp("zs",16));
        list.stream().sorted((x,y)->{
           if(x.name.equals(y.name)){
               return x.age.compareTo(y.age);
           }
           else return x.name.compareTo(y.name);
        }).forEach(System.out::println);
    }

3.5 stream_查找与匹配

Stream 的终止操作:
allMatch – 检查是否匹配所有元素
anyMatch – 检查是否至少匹配一个元素
noneMatch – 检查是否没有匹配所有元素
findFirst – 返回第一个元素
findAny – 返回当前流中的任意元素
cout – 返回流中元素的总个数
max – 返回流中最大值
min – 返回流中最小值

3.6 stream_归约与收集

3.6.1 规约

归约: reduce(T identity,BinaryOperator) / reduce(BinaryOperator) :可以将流中的元素反复结合起来,得到一个值,比如求一个数组的和

identity:起始值
BinaryOperator:二元运算

    @Test
    public void test(){
        List<Integer>list=Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer sum=list.stream().reduce(0,(x,y)->x+y);
        System.out.println(sum);
    }

注意:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名

3.6.2 收集

收集: collect(Collector<? super T, A, R> collector):将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法。例如,把一个公司的所有员工信息单独收集起来存到一个集合中去

collector:我们要按照哪种方式去收集

案例演示:

 @Test
    public void test(){
        List<Emp>list=Arrays.asList(new Emp("zs",10),new Emp("ls",15),new Emp("zs",16));
        List<String>l1=list.stream().map(Emp::getName).collect(Collectors.toList());
       // Collectors 里面有很多的工具
        for (String s : l1) {
            System.out.println(s);
        }
    }

4. 并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流

Java8 中将并行进行了优化,我们可以很容易对数据进行并行操作。Stream API 可以生命性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换

4.1 Fork/Join 框架

4.1.1 了解 Fork/Join 框架

Fork/Join 框架: 就是在必要的情况下,将一个大人物,进行拆分成若干个小任务(拆到不可再分时),再将一个个的小任务运算的结果进行 join 汇总

在这里插入图片描述

4.1.2 Fork/Join 框架与传统线程池的区别

Fork/Join 框架采用了“工作窃取”模式:
当执行新任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,当自己的线程队列执行完了之后,再从其他的一个随机线程队列中偷一个并把它放在自己的队列中

相对于一般的线程池实现,Fork/Join 框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在 Fork/Join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻求其他尚未运行的子问题来执行,这种方式减少了线程等待时间,提高性能

4.1.3 Fork/Join 框架案例

计算 1~ 1000000000

import java.util.concurrent.RecursiveTask;

public class ForkJoinCalculate extends RecursiveTask<Long> {

    Long start,end;
    Long thread=5000L;
    public ForkJoinCalculate(Long start,Long end) {
        this.start=start;
        this.end=end;
    }

    @Override
    protected Long compute() {
        Long len=end-start,ans=0L;
        if(len<=thread){
            for(Long i=start;i<end;i++) ans+=i;
            return ans;
        }
        else{
            Long mid=(start+end)/2;
            ForkJoinCalculate left=new ForkJoinCalculate(start,mid);
            ForkJoinCalculate right=new ForkJoinCalculate(mid,end);
            left.fork(); //拆分子任务,同时压入线程队列中
            right.fork();
            return left.join()+right.join();
        }
    }
}
public class ForkJoinTest {
    @Test
    public void test(){
        //要用到 ForkJoinPool 的池子
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long>forkJoinTask=new ForkJoinCalculate(0L,1000000000L);
        Long sum=pool.invoke(forkJoinTask);
        System.out.println(sum);
    }
}

5.Optional 类

Optional<T> 类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用 null 来表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常

常用方法

  • Optional.of(T t):创建一个 Optional 实例
  • Optional.empty():创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为空,创建 Optional 实例,否则创建空实例
  • isPresent():判断是否包含值
  • orElse(T t):如果调用对象包含值,返回该值,否则返回 t
  • orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f):如果有值对其处理,并返回处理后的 Optional ,否则返回 * * * * Optional.empty()
  • flatMap(Function mapper):与 map 类似,要求返回值必须是 Optional

6.接口中的默认方法与静态方法

6.1 接口中的默认方法

在接口中的方法前加上default关键字就可以在接口中写方法的默认实现。
默认方法,接口的子类不需要实现,可以直接使用
可以定义一个或多个默认方法


Java8为什么要引入默认方法?
默认方法的主要优势是提供一种拓展接口的方法,而不破坏现有代码。加入我们有一个已经投入使用接口需要拓展一个新的方法,在JDK8以前,如果为一个使用的接口增加一个新方法,则我们必须在所有实现类中添加该方法的实现,否则编译会出现异常。如果实现类数量少并且我们有权限修改,可能会工作量相对较少。如果实现类比较多或者我们没有权限修改实现类源代码,这样可能就比较麻烦。而默认方法则解决了这个问题,它提供了一个实现,当没有显示提供其他实现时就采用这个实现。这样新添加的方法将不会破坏现有代码。

默认方法的另一个优势是该方法是可选的,子类可以根据不同的需求Override默认实现。


接口默认方法的 “类优先” 原则
若接口中定义了一个默认方法,而另一个父类或接口中又定义了一个同名的方法时

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

7. 新时间日期API

和原来的时间日期API最大的不同就是以前不是线程安全的,现在的是线程安全的

8.重复注解与类型注解

就是在一个类上可以定义两个类型相同的注解

附上 Java8 实战的 pdf:
链接:https://pan.baidu.com/s/1AUHhyT7xDH1nsEjqR6lJWA
提取码:eju6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值