Java 8 新特性
一、Lambda表达式
1.函数式编程思想概述
面向对象编程思想
面向对象强调的是对象 , “必须通过对象的形式来做事情”,相对来讲比较复杂,有时候我们只是为了做某件事情而不得不创建一个对象 , 例如线程执行任务,我们不得不创建一个实现Runnable接口对象,但我们真正希望的是将run方法中的代码传递给线程对象执行
函数编程思想
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。例如线程执行任务 , 使用函数式思想 , 我们就可以通过传递一段代码给线程对象执行,而不需要创建任务对象
2.Lambda表达式的格式
Lambda表达式的作用就是简化代码,省略了面向对象中类和方法,对象的书写。
标准格式:
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda表达式的标准格式为:
(参数类型 参数名,参数类型 参数名,...) -> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
->
是新引入的语法格式,代表指向动作。- 大括号内的语法与传统方法体要求基本一致。
Lambda表达式的使用条件: 接口中有且仅有一个抽象方法的接口,才可以使用Lambda表达式
1.接口中只有一个抽象方法的接口,叫做函数式接口
2.如果是函数式接口,那么就可以使用@FunctionalInterface注解来标识
3.Lambda表达式省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参数,则小括号可以省略;
- 如果大括号内有且仅有一条语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
4.Lambda的前提条件和表现形式
-
Lambda的前提条件:
-
使用Lambda必须具有接口,且要求接口中的抽象方法有且仅有一个。(别的方法没有影响) (条件)
-
使用Lambda必须具有上下文推断。(忽略)
-
如果一个接口中只有一个抽象方法,那么这个接口叫做是函数式接口。
@FunctionalInterface这个注解 就表示这个接口是一个函数式接口
-
-
-
Lambda的表现形式
-
变量形式
变量的类型为函数式接口类型,那么可以赋值一个Lambda表达式
-
参数形式
方法的形参类型为函数式接口类型,那么就可以传入一个Lambda表达式 常见
-
返回值形式
法的返回值类型为函数式接口类型,那么就可以返回一个Lambda表达式 常见
-
二、Stream
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
例如: 有一个List集合,要求:
- 将List集合中姓张的的元素过滤到一个新的集合中
- 然后将过滤出来的姓张的元素,再过滤出长度为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.流式思想概述
- 搭建好函数模型,才可以执行
函数模型: 一定要有终结的方法,没有终结的方法,这个函数模型是不会执行的 - Stream流的操作方式也是流动操作的,也就是说每一个流都不会存储元素
- 一个Stream流只能操作一次,不能重复使用
- Stream流操作不会改变数据源
2.获取流方式
-
根据Collection获取流
Collection接口中有一个stream()方法,可以获取流 , default Stream stream():获取一个Stream流
- 通过List集合获取:
- 通过Set集合获取
-
根据Map获取流
-
使用所有键的集合来获取流
-
使用所有值的集合来获取流
-
使用所有键值对的集合来获取流
-
-
根据数组获取流
Stream流中有一个static Stream of(T… values)
- 通过数组获取:
- 通过直接给多个数据的方式
3.常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 终结方法:返回值类型不再是
Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那样的链式调用。本小节中,终结方法包括count
和forEach
方法。 - 非终结方法(延迟方法):返回值类型仍然是
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);
}
}