【系列教程】Java基础语法(十七):JDK特性
JDK8新特性
Lambda
函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“数据做操作“面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对像的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现
体验lambda表达式
需求:启动一个线程,在控制台输出一句话:多线程程序启动了
方式1
定义一个类MyRunnable:实现Runnable接口,重写run() 方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable的对象作为构造参数传递
启动线程
方式2
- 匿名内部类的方式改进
方式3
- Lambda表达式的方式改进
1 | //1.创建MyRunnable类 |
Lambda表达式的标准格式
匿名内部类中重写run() 方法的代码分析:
方法形式参数为空,说明调用方法时不需要传递参数
方法返回值类型为void,说明方法执行没有结果返回
方法体中的内容,是我们具体要做的事情
1 | //匿名内部类的方式改进 |
Lambda表达式的代码分析:
()
:里面没有内容,可以看成是方法形式参数为空->
:用箭头指向后面要做的事情{}
:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
组成Lambda表达式的三要素:形式参数,箭头,代码块
1 | //Lambda表达式的方式改进 |
Lambda表达式的格式:
格式:(形式参数)-> {代码块}
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
->:由英文中画线和大于符号组成,固定写法。代表指向动作
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
Lambda表达式的使用
Lambda表达式的使用前提:
有一个接口
接口中有且仅有一个抽象方法
练习1:
定义一个接口(Eatable),里面定义一个抽象方法:void eat();
定义一个测试类(EatableDemo),在测试类中提供两个方法:
一个方法是:useEatable(Eatable e)
一个方法是主方法,在主方法中调用useEatable方法
1 | //1.创建接口 |
练习2:
定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s):
定义一个测试类(FlyableDemo),在测试类中提供两个方法
一个方法是:useFlyable(Flyable f)
一个方法是主方法,在主方法中调用useFlyable方法
1 | //1.创建Flyable接口 |
练习3:
定义一个接口(Addable),里面定义一个抽象方法:int add(intx, inty);
定义一个测试类(AddableDemo),在测试类中提供两个方法:
- 一个方法是:useAddable(Addable a
- 一个方法是主方法,在主方法中调用useAddable方法
1 | //1.创建接口 |
Lambda表达式的省略模式
省略规则:
参数类型可以省略。但是有多个参数的情况下,不能只省略一个
如果参数有且仅有一个,那么小括号可以省略
如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
1 | //1.定义Addable接口 |
Lambda表达式的注意事项
注意事项:
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
- 根据局部变量的赋值得知Lambda对应的接口:Runnable r=()->System.out.println(“Lambda表达式”);
- 根据调用方法的参数得知Lambda对应的接口:new Thread(()->System.out.println(“Lambda表达式”).start();
Lambda表达式和匿名内部类的区别
1.所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是接口
2.使用限制不同
如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
如果接口中有多个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
3.实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
stream 流
什么是 Stream ? 这里的 Stream 不同于 io 中的 InputStream 和 OutputStream,Stream 位于包 java.util.stream 中, 也是 java 8 新加入的,Stream 只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。
Stream 支持操作主要有 map, reduce, 排序,最大值,最小值等。
Stream 的几个特征:
- 单次处理。一次处理结束后,当前Stream就关闭了。
- 支持并行操作
常见的获取 Stream 的方式
- 从集合中获取
- Collection.stream();
- Collection.parallelStream();
- 静态工厂
- Arrays.stream(array)
- Stream.of(T …)
- IntStream.range()
Optional 类
先说一下 Optional 类, 因为 Stream 中有些操作返回的 Optional 类,而不是对象本身。Optional 类始于 jdk 1.8,Optional 是对具体对象的简单的包装,并提供了部分操作 主要目的是防止产生空指针异常。如果以前了解过 Null Object Pattern,就很容易了解 Optional 存在的意义。
主要方法:
- get 返回包装的对象
- isPresent 判断是否包装了对象
- ifPresent(Consumer) 如果存在包装对象,则对包装对象进行一定的操作
allMatch
使用给定的 Predicate 检查 Stream 中的所有元素,全部都通过检测则返回 true,否则 false 。
1 | System.out.println(Stream.of(1,2,3).allMatch(n -> n >= 1)); |
anyMatch
使用给定的 Predicate 检查 Stream 中的所有元素,至少有一个通过检测则返回 true,否则 false 。
collect
collect 操作使用给定的 Collector 做 reduce 操作。
数组元素连接
1 | System.out.println(Stream.of("A", "B", "C").collect(Collectors.joining(","))); |
转成 List
1 | List asList = Stream.of("A", "B", "C").collect(Collectors.toList()); |
根据城市分组
1 | Map> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity)); |
根据州和城市分组
1 | Map>> peopleByStateAndCity |
count
返回 Stream 中的元素总数。
1 | System.out.println(Stream.of(1,2,3).count()); |
distinct
返回唯一的元素列表,类似于 数据库 sql 中的 distinct 关键字。 比较时通过 equals 方法来判定是否相同。
1 | System.out.println(Stream.of(1,2,3,3).distinct().map(n -> n + "").collect(Collectors.joining(","))); |
filter
使用给定的 Predicate 的筛选 Stream 元素,符合条件的留下并组成一个新的 Stream 。
1 | System.out.println(Stream.of(1,2,3).filter(n -> n > 1).map(n -> n + "").collect(Collectors.joining(","))); |
findAny
返回任何一个不确定的元素,通过 Optional 来包装。如果在一个固定不变的组合中,返回第一个元素。
1 | System.out.println(Stream.of(1,2,3).findAny().get()); |
findFirst
返回第一个元素。
1 | System.out.println(Stream.of(1,2,3).findFirst().get()); |
flatMap
适用于如果Stream中的元素还是集合,能将集合中的元素组成一个平面的集合。简单来下面的例子,Stream 是二维的,因为 Stream 的元素还是数组,经过flag处理后,变成一维的了,所有元素位于一个Stream 下了。
1 | System.out.println( |
forEach
逐个元素执行 Consumer 操作。
1 | Stream.of(1,2,3).forEach(n -> System.out.print(n + ",")); |
limit
取出指定个数的元素组成新的 Stream .
1 | System.out.println(Stream.of(1,2,3).limit(2).map(n -> n + "").collect(Collectors.joining(","))); |
map
map 方法的作用是依次对 Stream 中的元素进行指定的函数操作,并将按顺序将函数操作的返回值组合到一个新的 Stream 中。
下面例子将每个元素的值 +1
1 | System.out.println(Stream.of(1,2,3).map(n -> n + 1).map(String::valueOf).collect(Collectors.joining(","))); |
max
max 通过给定的比较器,将最大的元素取出来,返回 Optional
1 | System.out.println(Stream.of(1,2,3).max((a, b) -> a - b).get()); |
min
min 通过给定的比较器,将最小的元素取出来,返回 Optional
1 | System.out.println(Stream.of(1,2,3).min((a, b) -> a - b).get()); |
noneMatch
noneMatch 于 allMatch, anyMatch 类似,使用给定的 Predicate 检查 Stream 中的所有元素,全部不通过检测则返回 true,否则 false 。
1 | System.out.println(Stream.of(1,2,3).noneMatch(n -> n > 1)); |
reduce
reduce 的函数操作为二元操作符,一个为前面操作的结果,一个为当前元素,reduce 会逐个对 Stream 中的元素执行指定的操作,并返回最终的结果。
如求和
1 | System.out.println(Stream.of(1,2,3).reduce(0, (a, b) -> a + b)); |
skip
忽略给定个数的元素,返回剩下的元素组成 Stream 。
1 | System.out.println(Stream.of(1,2,3).skip(1).map(n -> n + "").collect(Collectors.joining(","))); |
sorted
通过给定的比较器排序,将排序后的元素的 Stream 返回。
1 | System.out.println(Stream.of(1,2,3).sorted().map(n -> n + "").collect(Collectors.joining(","))); |
方法引用
方法引用概述
- 方法引用使用一对冒号 :: , 方法引用就是用来在一定的情况下,替换Lambda表达式
方法引用基本使用
- 使用场景:
- 如果一个Lambda表达式大括号中的代码和另一个方法中的代码一模一样,那么就可以使用方法引用把该方法引过来,从而替换Lambda表达式
- 如果一个Lambda表达式大括号中的代码就是调用另一方法,那么就可以使用方法引用把该方法引过来,从而替换Lambda表达式
1 | public class Test { |
方法引用的分类
构造方法引用
1 | public class Test2 { |
静态方法引用
1 | public class Test2 { |
对象成员方法引用
成员方法有参数
1 | public class Test2 { |
类的成员方法引用
成员方法没有参数
1 | public class Test2 { |
小结
1 | 总结:使用方法引用的步骤 |
Base64
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
作为分割。编码输出最后没有行分割。
- 基本:输出被映射到一组字符
获取编码器和解码器的方法
1
2
3
4
5
6
7
8static 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 编码器。编码和解码的方法:
1
2Encoder编码器: encodeToString(byte[] bys)编码
Decoder解码器: decode(String str) 解码
案例演示
- 基本
1 | public class Test1 { |
- URL
1 | public class Test2 { |
- MIME
1 | public class Test3 { |
StringJoiner使用
jdk8-17新特性
Java Record
Java14 中预览的新特性叫做 Record,在 Java 中,Record 是一种特殊类型的 Java 类。可用来创建不可变类,语法简短。参考JEP 395. Jackson 2.12 支持 Record 类。
- 任何时候创建 Java 类,都会创建大量的样板代码,我们可能做如下:
- 每个字段的 set,get 方法
- 公共的构造方法
- 重写 hashCode, toString(), equals()方法
Java Record 避免上述的样板代码,如下特点:
- 带有全部参数的构造方法
- public 访问器
- toString(),hashCode(),equals()
- 无 set,get 方法。没有遵循 Bean 的命名规范
- final 类,不能继承 Record,Record 为隐士的 final 类。除此之外与普通类一样
- 不可变类,通过构造创建 Record
- final 属性,不可修改
- 不能声明实例属性,能声明 static 成员
- IDEA 创建新的 Maven 工程 Lession01-feature
相当于Jdk编译器(语言级别)内置的Lombok(编译级别)
上手:
IDEA 新建 Class,选择类 Record
Record 通过构造方法创建了只读的对象,能够读取每个属性,不能设置新的属性值。
Record 用于创建不可变的对象,同时减少了样板代码。
Record 对每个属性提供了 public 访问器,例如 lisi.name()
1 | package com.zuoer.demo.model; |
Record 与 Lombok
Java Record 是创建不可变类且减少样板代码的好方法。Lombok 是一种减少样板代码的工具。两者有表面上的重叠部分。可能有人会说 Java Record 会代替 Lombok. 两者是有不同用途的工具。
Lombok 提供语法的便利性,通常预装一些代码模板,根据您加入到类中的注解自动执行代码模板。这样的库纯粹是为了方便实现 POJO 类。通过预编译代码。将代码的模板加入到 class 中。
Java Record 是语言级别的,一种语义特性,为了建模而用,数据聚合。简单说就是提供了通用的数据类,充当“数据载体”,用于在类和应用程序之间进行数据传输。
Record实现接口
实体类直接实现接口
1 | interface Person { |
Record作为局部对象使用
在代码块中定义并使用 Record
1 | public String say(String name) { |