Java 8 新特性

Java 8 新特性

一、Lambda表达式

1.函数式编程思想概述
面向对象编程思想

面向对象强调的是对象 , “必须通过对象的形式来做事情”,相对来讲比较复杂,有时候我们只是为了做某件事情而不得不创建一个对象 , 例如线程执行任务,我们不得不创建一个实现Runnable接口对象,但我们真正希望的是将run方法中的代码传递给线程对象执行

函数编程思想

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。例如线程执行任务 , 使用函数式思想 , 我们就可以通过传递一段代码给线程对象执行,而不需要创建任务对象

2.Lambda表达式的格式

Lambda表达式的作用就是简化代码,省略了面向对象中类和方法,对象的书写。

标准格式:

Lambda省去面向对象的条条框框,格式由3个部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为:

(参数类型 参数名,参数类型 参数名,...) -> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
  • ->是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

Lambda表达式的使用条件: 接口中有且仅有一个抽象方法的接口,才可以使用Lambda表达式
1.接口中只有一个抽象方法的接口,叫做函数式接口
2.如果是函数式接口,那么就可以使用@FunctionalInterface注解来标识

3.Lambda表达式省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略;
  2. 如果小括号内有且仅有一个参数,则小括号可以省略;
  3. 如果大括号内有且仅有一条语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
4.Lambda的前提条件和表现形式
  • Lambda的前提条件:

    • 使用Lambda必须具有接口,且要求接口中的抽象方法有且仅有一个。(别的方法没有影响) (条件)

    • 使用Lambda必须具有上下文推断。(忽略)

      • 如果一个接口中只有一个抽象方法,那么这个接口叫做是函数式接口。

        @FunctionalInterface这个注解 就表示这个接口是一个函数式接口

  • Lambda的表现形式
    • 变量形式

      变量的类型为函数式接口类型,那么可以赋值一个Lambda表达式

    • 参数形式

      方法的形参类型为函数式接口类型,那么就可以传入一个Lambda表达式 常见

    • 返回值形式

      法的返回值类型为函数式接口类型,那么就可以返回一个Lambda表达式 常见

二、Stream

在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

例如: 有一个List集合,要求:

  1. 将List集合中姓张的的元素过滤到一个新的集合中
  2. 然后将过滤出来的姓张的元素,再过滤出长度为3的元素,存储到一个新的集合中

传统方式操作集合

public class Demo {
    public static void main(String[] args) {
        // 传统方式操作集合:
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张杰");
        list.add("张三丰");

        // 1.将List集合中姓张的的元素过滤到一个新的集合中
        // 1.1 创建一个新的集合,用来存储所有姓张的元素
        List<String> listB = new ArrayList<>();

        // 1.2 循环遍历list集合,在循环中判断元素是否姓张
        for (String e : list) {
            // 1.3 如果姓张,就添加到新的集合中
            if (e.startsWith("张")) {
                listB.add(e);
            }
        }

        // 2.然后将过滤出来的姓张的元素,再过滤出长度为3的元素,存储到一个新的集合中
        // 2.1 创建一个新的集合,用来存储所有姓张的元素并且长度为3
        List<String> listC = new ArrayList<>();

        // 2.2 循环遍历listB集合,在循环中判断元素长度是否为3
        for (String e : listB) {
            // 2.3 如果长度为3,就添加到新的集合中
            if(e.length() == 3){
                listC.add(e);
            }
        }

        // 3.打印所有元素---循环遍历
        for (String e : listC) {
            System.out.println(e);
        }
    }
}

Stream流操作集合

public class Demo {
    public static void main(String[] args) {
        // 体验Stream流:
        list.stream().filter(e->e.startsWith("张")).filter(e->e.length()==3).forEach(e-> System.out.println(e));
        System.out.println(list);
    }
}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

1.流式思想概述
  1. 搭建好函数模型,才可以执行
    函数模型: 一定要有终结的方法,没有终结的方法,这个函数模型是不会执行的
  2. Stream流的操作方式也是流动操作的,也就是说每一个流都不会存储元素
  3. 一个Stream流只能操作一次,不能重复使用
  4. Stream流操作不会改变数据源
2.获取流方式
  1. 根据Collection获取流

    Collection接口中有一个stream()方法,可以获取流 , default Stream stream():获取一个Stream流

    1. 通过List集合获取:
    2. 通过Set集合获取
  2. 根据Map获取流

    • 使用所有键的集合来获取流

    • 使用所有值的集合来获取流

    • 使用所有键值对的集合来获取流

  3. 根据数组获取流

    Stream流中有一个static Stream of(T… values)

    • 通过数组获取:
    • 通过直接给多个数据的方式
3.常用方法

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

  • 终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括countforEach方法。
  • 非终结方法(延迟方法):返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
函数拼接与终结方法

在上述介绍的各种方法中,凡是返回值仍然为Stream接口的为函数拼接方法,它们支持链式调用;而返回值不再为Stream接口的为终结方法,不再支持链式调用。如下表所示:

方法名方法作用方法种类是否支持链式调用
count统计个数终结
forEach逐一处理终结
filter过滤函数拼接
limit取用前几个函数拼接
skip跳过前几个函数拼接
map映射函数拼接
concat组合函数拼接

备注:本小节之外的更多方法,请自行参考API文档。

forEach : 逐一处理

虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的

void forEach(Consumer<? super T> action);

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。例如:

// 函数模型: 获取流->逐一消费流中的元素
list.stream().forEach((String e)->{System.out.println(e);});

// 并行流: 通过Collection的parallelStream()方法可以得到并行流
list.parallelStream().forEach((String e)->{System.out.println(e);});
count:统计个数

正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数:

long count();

该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:

long count = list.stream().count();
filter:过滤

可以通过filter方法将一个流转换成另一个子集流。方法声明:

Stream<T> filter(Predicate<? super T> predicate);

该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

基本使用

Stream流中的filter方法基本使用的代码如:

// 需求:过滤出姓张的元素
stream.filter((String s) -> {
   return s.startsWith("张");
   }).forEach((String name)->{
   System.out.println(name);
   });

在这里通过Lambda表达式来指定了筛选的条件:必须姓张。

limit:取用前几个

limit方法可以对流进行截取,只取用前n个。方法签名:

Stream<T> limit(long maxSize);

参数是一个long型,如果流的当前长度大于参数则进行截取;否则不进行操作。基本使用:

// 需求: 保留前3个元素
stream1.limit(7).forEach(name-> System.out.println(name));
skip:跳过前几个

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

// 需求: 跳过前3个元素
stream.skip(3).forEach(name-> System.out.println(name));
map:映射

如果需要将流中的元素映射到另一个流中,可以使用map方法。方法签名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

基本使用

Stream流中的map方法基本使用的代码如:

// 需求:把stream1流中的元素转换为int类型
stream1.map((String s)->{return Integer.parseInt(s);}).forEach((Integer i)->{
    System.out.println(i+1);
});

这段代码中,map方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer类对象)。

concat:组合

如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

备注:这是一个静态方法,与java.lang.String当中的concat方法是不同的。

该方法的基本使用代码如:

// 需求:合并stream1和stream2
Stream<String> stream = Stream.concat(stream1, stream2);
4.收集Stream结果
收集到集合中
  • Stream流中提供了一个方法,可以把流中的数据收集到单列集合中

    • <R,A> R collect(Collector<? super T,A,R> collector): 把流中的数据收集到单列集合中

      • 参数Collector<? super T,A,R>: 决定把流中的元素收集到哪个集合中

      • 返回值类型是R,也就是说R指定为什么类型,就是收集到什么类型的集合

      • 参数Collector如何得到? 使用java.util.stream.Collectors工具类中的静态方法:

        • public static Collector<T, ?, List> toList():转换为List集合。

          //收集到List单列集合中
          List<String> list1 = stream.collect(Collectors.toList());
          
        • public static Collector<T, ?, Set> toSet():转换为Set集合。

          // 收集到Set单列集合中
          Set<String> set = stream.collect(Collectors.toSet());
          
收集到数组中

Stream提供toArray方法来将结果放到一个数组中,返回值类型是Object[]的:

Object[] toArray();

Object[] arr = stream.toArray();

三、方法引用

方法引用使用一对冒号 :: , 方法引用就是用来在一定的情况下,替换Lambda表达式

1.基本使用
  • 如果一个Lambda表达式大括号中的代码和另一个方法中的代码一模一样,那么就可以使用方法引用把该方法引过来,从而替换Lambda表达式
  • 如果一个Lambda表达式大括号中的代码就是调用另一方法,那么就可以使用方法引用把该方法引过来,从而替换Lambda表达式
2.方法引用的分类
1.构造方法引用

构造方法: 类名::new

// 需求: 把集合中的元素转换为Person对象,打印输出
list.stream().map(s-> new Person(s)).forEach(s-> System.out.println(s));
2.静态方法引用

静态方法: 类名::方法名

// 需求:把集合中的元素转换为int类型,打印输出
list.stream().map(s-> Integer.parseInt(s)).forEach(s-> System.out.println(s));
3.对象成员方法引用: 带参数

成员方法(有参数): 对象名::方法名

// 需求:把集合中所有元素打印输出
list.stream().forEach(s-> System.out.println(s));
4.对象成员方法引用:不带参数

类的成员方法\成员方法(无参数): 类名::方法名

// 需求: 把集合中的元素转换为该元素对应的字符长度,打印输出
list.stream().map(s->s.length()).forEach(System.out::println);

四、Base64

Base64是jdk8提出的一个新特性,可以用来进行按照一定规则编码和解码

Base64编码和解码的相关方法
  • 编码的步骤:

    • 获取编码器
    • 调用方法进行编码
  • 解码步骤:

    • 获取解码器
    • 调用方法进行解码
  • Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

    • **基本:**输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
    • **URL:**输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
    • **MIME:**输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。
  • 获取编码器和解码器的方法

    static Base64.Decoder getDecoder() 基本型 base64 解码器。
    static Base64.Encoder getEncoder() 基本型 base64 编码器。
    
    static Base64.Decoder getMimeDecoder() Mime型 base64 解码器。
    static Base64.Encoder getMimeEncoder() Mime型 base64 编码器。
    
    static Base64.Decoder getUrlDecoder() Url型 base64 解码器。
    static Base64.Encoder getUrlEncoder() Url型 base64 编码器。
    
  • 编码和解码的方法:

    Encoder编码器:  encodeToString(byte[] bys)编码
    Decoder解码器:  decode(String str) 解码
    
使用演示
public class Test1 {
    public static void main(String[] args) {
        // 使用基本型的编码器和解码器对数据进行编码和解码:
        // 1.获取编码器
        Base64.Encoder encoder = Base64.getEncoder();

        // 2.对字符串进行编码
        String str = "name=中国?password=123456";
        String str1 = encoder.encodeToString(str.getBytes());

        // 3.打印输出编码后的字符串
        System.out.println("编码后的字符串:"+str1);

        // 4.获取解码器
        Base64.Decoder decoder = Base64.getDecoder();

        // 5.对编码后的字符串进行解码
        byte[] bys = decoder.decode(str1);
        String str2 = new String(bys);

        // 6.打印输出解码后的字符串
        System.out.println("解码后的字符串:"+str2);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值