十七、JDK8 新特性(更新)
十七、JDK8 新特性
17.1 JDK8新特性分类
前面已经学过的JDK8新特性介绍:
- ✅默认方法:见 10.8.2 接口的语法
- ✅新的日期API:见 13.9.3 第三代日期类 LocalDateTime
本章将学习的 JDK8
新特性:
- ✅
Lambda
表达式 - ✅函数式接口
- ✅方法引用
- ✅
Stream
流 - ✅
Base64
编码表 - ⬜其他的
JDK
新特性见:菜鸟教程 JDK8新特性
17.2 Lambda表达式介绍
17.2.1 Lambda表达式案例
先来看案例:
package com.itheima.lambda;
/**
* @author: Carl Zhang
* @create: 2021-12-31 14:57
* 体验Lambda表达式和传统表达式区别
* 实现游泳
*/
public class Lambda01 {
public static void main(String[] args) {
/*
* 结论:1. 传统的匿名内部类方法解决接口参数问题,需要 创建匿名内部类对象,实现接口方法,两步 --关注点 怎么做
* 2. Lambda表达式只需要一条式子,代码简介,关注点更明确在方法功能和输出 -- 关注点 做什么
* 3. 这种关注方法能做什么的思想就是函数式编程思想
* */
//传统方法实现游泳 - 匿名内部类
swim(new Swim() {
@Override
public void swimming() {
System.out.println("匿名内部类的游泳....");
}
});
//Lambda表达式实现
swim(() -> System.out.println("匿名内部类的游泳...."));
}
public static void swim (Swim swim) {
swim.swimming();
}
}
interface Swim {
/**
* 游泳
* */
void swimming();
}
注意:lambda
表达式可以理解为对匿名内部类的一种简化 , 但是本质是有区别的
17.2.2 引入函数式编程思想
介绍:
- 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
- 而
lambda
是就是函数式编程思想的一种体现
17.2.3 函数式编程思想和面向对象编程思想的对比
- 面向对象思想 :
- 强调的是用对象去完成某些功能 -- 怎么做
- 函数式编程思想 :
- 强调的是结果 , 而不是怎么去做 -- 做什么
17.3 函数式接口
17.3.1 函数式接口介绍
- 概念:
- 只有一个抽象方法需要重写的接口就是函数式接口。
- 函数式接口是允许有其他的非抽象方法的存在例如静态方法,默认方法,私有方法。
- 注解: 为了标识接口是一个函数式接口,可以在接口之上加上一个注解:
@FunctionalInterface
- 相关API :
JDK
中的java.util.function
包里的接口都是函数式接口
我们以前学的 Runnable
接口也是函数式接口
也可以自定义一个函数式接口:
package com.itheima.lambda;
/**
* @author CarlZhang
* 自定义一个函数式接口
*/
@SuppressWarnings("ALL")
@FunctionalInterface
public interface FunctionalInterface01 {
/**
* 只有一个 要重写的 抽象方法
*/
void method01();
/**
* 继承Object类的方法
*/
@Override
String toString();
/**
* jdk1.8 接口里可以有静态方法和默认方法
* */
static void method02() {
System.out.println("FunctionalInterface01接口里的静态方法");
}
/**
* 默认方法
* */
default void method03() {
System.out.println("FunctionalInterface01接口里的默认方法");
}
}
@FunctionalInterface
interface FunctionalInterface02 extends FunctionalInterface01{
//报错:因为此处有两个要重写的抽象法, 一个父类接口的一个此接口的,
//结论:函数式接口里只能有一个要重写的抽象方法,父类接口里的也算,而Object类比较特殊,
// 接口里有重写Object类的抽象方法不影响函数式接口的判定
//void method02();
}
17.3.2 注意事项和使用细节
- 接口里只能有一个要重写的抽象方法,继承自
**Object**
类的方法除外 - 可以用注解
@FunctionalInterface
来表示函数式接口
17.4 Lambda表达式的使用
17.4.1 Lambda表达式语法
标准格式 :(形参列表) - > { //要实现方法的方法体... }
17.4.2 Lambda表达式使用案例
/**
* @author: Carl Zhang
* @create: 2021-12-31 16:32
* 练习1:
* 1 编写一个接口(ShowHandler)
* 2 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
* 3 在测试类(ShowHandlerDemo)中存在一个方法(useShowHandler)
* 方法的的参数是ShowHandler类型的,在方法内部调用了ShowHandler的show方法
*/
public class ShowHandlerDemo {
public static void useShowHandler(ShowHandler showHandler) {
showHandler.show();
}
public static void main(String[] args) {
//调用useShowHandler方法
//使用Lambda表达式实现ShowHandler接口作为参数
useShowHandler(() -> {
System.out.println("使用Lambda表达式实现ShowHandler接口作为参数");
});
}
/*
* Lambda表达式格式解析
* 1. () 表示实现的接口里方法的形参列表
* 2. -> 语法规定,指向要实现的方法内容
* 3. {} 要实现的方法的方法体
*
* 注意:Lambda表达式实现的接口必须是函数式接口
* */
}
/**
* @author CarlZhang
*/
@FunctionalInterface
public interface ShowHandler {
/**
* 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
* */
void show();
}
/**
* @author: Carl Zhang
* @create: 2021-12-31 16:48
* 需求
* 1 首先存在一个接口(StringHandler)
* 2 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
* 3 在测试类(StringHandlerDemo)中存在一个方法(useStringHandler),
* 方法的的参数是StringHandler类型的,
* 在方法内部调用了StringHandler的printMessage方法
*/
public class StringHandlerDemo {
public static void useStringHandler(StringHandler stringHandler) {
stringHandler.printMessage("Hello, World");
}
public static void main(String[] args) {
//使用lambda表达式实现StringHandler, 作为参数传递
//结论:
// 1. () 里的内容对应接口里方法()的内容,是形式参数,lambda表达式看作一个接口实现类
// 2. 只有一个参数情况下,可以省略()
useStringHandler(String s -> {
System.out.println("调用Lambda表达式的代码块 " + s);
});
//匿名内部类的方式实现
useStringHandler(new StringHandler() {
@Override
public void printMessage(String s) {
System.out.println("匿名内部类的方法 " + s);
}
});
}
}
@FunctionalInterface
public interface StringHandler {
/**
* 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
* @param s 任意字符串
*/
void printMessage(String s);
}
package com.heima.lambda;
import org.omg.CORBA.PUBLIC_MEMBER;
/**
* @author Carl Zhang
* @description
* @date 2022/1/1 20:32
* 1 首先存在一个接口(Calculator)
* 2 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
* 3 在测试类(CalculatorDemo)中存在一个方法(useCalculator)
* 方法的的参数是Calculator类型的
* 在方法内部调用了Calculator的calc方法
*/
public class CalculatorDemo {
public static void useCalculator(Calculator calculator) {
System.out.println(calculator.calc(11, 12));
}
public static void main(String[] args) {
/*
* 1. 有参有返回值的方法,直接写(形参列表) -> { return 返回值; },
* 进一步体现 (输入) - > {输出} 的函数式编程思想
* 2. 参数类型可以省略,有多个参数不能只省略一个
* 3. 代码块只有一句,则可以省略大括号和分号,甚至return
* */
//useCalculator((int num1, int num2) -> {
// return num1 + num2;
//});
useCalculator((num1, num2) -> num1 + num2);
}
}
/**
* @author CarlZhang
* 1 首先存在一个接口(Calculator)
* 2 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
*/
@FunctionalInterface
public interface Calculator {
/**
* 计算两数之和
* @param num1 第一个数
* @param num2 第二个数
* @return 两数之和
*/
int calc(int num1, int num2);
}
17.4.3 注意事项和使用细节
使用前提 :Lambda
表达式实现的接口必须是函数式接口
格式解析:
Lambda
表达式可看做函数式接口的一个实现类对象()
表示实现的接口里方法的形参列表,没有可以空着。- 参数类型可以省略,有多个参数不能只省略一个
- 只有一个参数可以省略
()
->
语法规定,指向要实现的方法内容{}
要实现的方法的方法体- 代码块里只有一句,则可以省略
{}
和;
,甚至return
- 代码块里只有一句,则可以省略
(形参) -> {返回值}
的格式体现了(输入) -> {输出}
的函数式编程思想
17.4.3 Lambda表达式和匿名内部类的区别
- 作用对象不同 :
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式
:只能是函数式接口
- 使用场景不同 :
- 如果接口中有且仅有一个抽象方法,可以使用
Lambda
表达式,也可以使用匿名内部类 - 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用
Lambda
表达式
- 如果接口中有且仅有一个抽象方法,可以使用
- 实现原理不同 :
- 匿名内部类:编译之后,产生一个单独的
.class
字节码文件 Lambda
表达式:编译之后,没有一个单独的.class
字节码文件。对应的字节码会在运行的时候动态生成
- 匿名内部类:编译之后,产生一个单独的
/**
* @author Carl Zhang
* @description Lambda表达式和匿名内部类的区别
* @date 2022/1/1 21:36
*/
public class LambdaVsAnonymous {
public static void main(String[] args) {
//Lambda表达式调用show方法 -- 编译后没有.class文件
Test test = () -> System.out.println("Hello, World");
test.show();
//匿名内部类调用show方法 -- 有LambdaVsAnonymous$1.class文件
new Test() {
@Override
public void show() {
System.out.println("Hello, World");
}
}.show();
}
}
@FunctionalInterface
interface Test {
/**
* 打印方法
*/
void show();
}
17.5 方法引用 [ 了解 ]
17.5.1 方法引用概述
- 当使用
Lambda
实现一个逻辑时,如果这个逻辑已经在某个类中存在相同逻辑的方法 ,可以直接引用此功能而不需要写Lambda
。 - 我们可以理解为
Lambda
用来简化匿名内部类 , 方法引用是用来简化Lambda
。
17.5.2 方法引用的分类
- 方法引用的符号 , 是双冒号
::
- 简单操作即可 :有方法可以引用的时候,会有黄色警告。只要按Alt+Enter
| 种类 | 语法格式 |
| --- | --- |
| 静态方法引用 | 类名 :: 静态方法名 |
| 构造方法引用 | 类名 :: new |
| 类的任意对象的方法引用 | 类名 :: 实例方法名 |
| 特定对象的方法引用 | 对象 :: 实例方法名 |
17.5.3 第一种 : 静态方法引用
lambda
表达式要实现的功能 , 和某个类的静态方法业务逻辑是一样 , 可以使用静态方法引用
格式:类名 :: 静态方法名
实践:
- 已知函数式接口存在一个方法 , 使用方法引用实现此方法
public interface MathTool{
int max(int a,int b);
}
- 要实现的功能 max 和
java.lang.Math
中的静态方法逻辑是一样的。
public class Demo01 {
public static void main(String[] args) {
//匿名内部类
MathTool m1 = new MathTool() {
@Override
public int max(int a, int b) {
return a > b ? a : b;
}
};
//Lambda
MathTool m2 = (a, b) -> a > b ? a : b;
//方法引用
MathTool m3 = Math::max;
System.out.println(m1.max(10,20));
System.out.println(m2.max(10,20));
System.out.println(m3.max(10,20));
}
}
17.5.4 第二种 : 构造方法引用
- 当Lambda表达式要实现的功能是创建指定的对象 ,恰好与某个构造方法功能一样 , 就可以使用构造方法的引用
格式 :类名::new
**实践 **
- 在Stream流中,将流中字符串转换为一个学生类型的对象
- Stream流中的map方法可以将流中的数据封装成对象
// 学生类
class Student {
private String name;
// 接收一个字符串 , 并将字符串创建成学生对象
public Student(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
public void show() {
System.out.println("我是Student类的show方法....");
}
}
import java.util.function.Function;
import java.util.stream.Stream;
public class ConstructorMethodDemo {
public static void main(String[] args) {
// 匿名内部类
Stream.of("迪丽热巴", "李沁", "柳岩").map(
new Function<String, Student>() {
@Override
public Student apply(String s) {
return new Student(s);
}
}
).forEach(System.out::println);
// lambda表达式改进
Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach(
(Student s) -> System.out.println(s)
);
// 方法引用改进
Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach((Student s) -> System.out.println(s));
}
}
17.5.5 第三种 : 任意对象的同一方法引用
- 类的任意对象调用一个无参方法时 可以使用
格式:类名 :: 方法名
实践:
- Stream流中遍历拿到一个类的多个对象 ,多个对象调用同一个无参方法
- 需求 : 把数据 "张三", "李四", "王五" 存储到Stream流中 , 使用,map方法封装成学生对象
使用每一个学生对象调用show方法
import java.util.stream.Stream;
public class ObjectMethodDemo {
public static void main(String[] args) {
// lambda表达式改进
Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach(
(Student s) -> {
s.show();
}
);
// 方法引用
Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach(
Student::show
);
}
}
17.5.6 第四种 : 指定对象的方法引用
格式:对象::方法
实践:
- 流遍历打印输出的简写
举例 : System.out::println : 输出语句的写法就是特定对象的方法引用
lambda表达式 : (String s) -> {System.out.println(s)}
此Lambda表达式是为了接受一个数据 , 把这个数据打印在控制台
System.out是一个对象 , 此对象中有一个方法叫做println方法 , println方法也是接受一个数据并打印在控制台
方法引用存在的目的是为了简化lambda表达式的 , 那么就可以使用System.out对象去引用println方法替代Lambda表达式
(String s) -> {System.out.println(s)}
方法引用: System.out :: println
- 需求 : 把 "张三" , "李四" , "王五" 字符串存储在Stream流中 , 并输出打印 , 使用方法引用完成
import java.util.stream.Stream;
public class ObjectMethodDemo {
public static void main(String[] args) {
Stream.of( "张三" , "李四" , "王五").forEach(
(String s) -> {
System.out.println(s);
}
);
Stream.of( "张三" , "李四" , "王五").forEach(
System.out::println
);
}
}
17.6 Stream 流
17.6.1 Stream的体验
import java.util.ArrayList;
/**
* @author Carl Zhang
* @description 体验Stream流的好处
* @date 2022/1/2 17:28
* 需求:按照下面的要求完成集合的创建和遍历
* <p>
* 1 创建一个集合,存储多个字符串元素
* "张无忌" , "张翠山" , "张三丰" , "谢广坤" , "赵四" , "刘能" , "小沈阳" , "张良"
* 2 把集合中所有以"张"开头的元素存储到一个新的集合
* 3 把"张"开头的集合中的长度为3的元素存储到一个新的集合
* 4 遍历上一步得到的集合
*/
public class Stream01 {
public static void main(String[] args) {
//集合的方式
//1.创建集合,添加数据
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张翠山");
list.add("张三丰");
list.add("谢广坤");
list.add("赵四");
list.add("刘能");
list.add("小沈阳");
list.add("张良");
ArrayList<String> newList = new ArrayList<>();
ArrayList<String> newList2 = new ArrayList<>();
//"张"开头的元素添加到新集合
for (String s : list) {
if (s.startsWith("张")) {
//3."张"开头的元素添加到新集合
newList.add(s);
}
}
//"张"开头的且长度为3的添加到另一个元素
for (String s : newList) {
if (s.startsWith("张") && s.length() == 3) {
newList2.add(s);
}
}
//4.打印
System.out.println(newList);
System.out.println(newList2);
System.out.println("===================");
//Stream流的方式 获取并打印"张"开头的且长度为3的元素 -- 使对容器里数据的操作进行了简化
list.stream().filter(s -> s.startsWith("张") && s.length()
== 3).forEach(s -> System.out.println(s));
}
}
17.6.2 Stream流介绍
17.7 Stream流三类方法
17.7.1 Stream流三类方法介绍
- 获取
Stream
流- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作。
- 一次操作完毕之后,还可以继续进行其他操作
- 终结方法
- 一个
Stream
流只能有一个终结方法 - 是流水线上的最后一个操作
- 一个
17.7.2 Stream流 - 获取方法
- 单列集合
- 可以使用
Collection
接口中的默认方法stream()
生成流 default Stream<E> stream()
- 可以使用
- 双列集合
- 双列集合不能直接获取 , 需要间接的生成流
- 可以先通过
keySet
() 或者entrySet()
获取一个Set
集合,再获取Stream
流
- 数组
Arrays
中的静态方法stream
生成流
import java.util.*;
import java.util.stream.Stream;
/**
* @author Carl Zhang
* @description Stream流的获取方法
* @date 2022/1/2 17:59
*/
@SuppressWarnings("ALL")
public class StreamGetMethod {
public static void main(String[] args) {
//获取单列集合的Stream流
singleSetStream();
//获取双列集合的Stream流
doubleSetStream();
//获取数组的Stream流
arrayStream();
//获取任意元素的stream流 --了解
int[] array = {1, 2, 3, 4, 5, 6};
Stream.of(array).forEach(i -> System.out.println(i)); //[I@7ba4f24f
Stream.of(1, 2, 3, 4, 5, 6).forEach(i -> System.out.println(i));
}
private static void arrayStream() {
System.out.println("获取数组的Stream流");
int[] arr = {1, 2, 3, 4, 5, 6};
Arrays.stream(arr).forEach(i -> System.out.println(i));
}
private static void doubleSetStream() {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("it001", "曹植");
hashMap.put("it002", "曹丕");
hashMap.put("it003", "曹熊");
hashMap.put("it004", "曹冲");
hashMap.put("it005", "曹昂");
// 双列集合不能直接获取 , 需要间接的生成流
// 可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
System.out.println("获取双列集合的Stream流");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
entries.stream().forEach(entry -> System.out.println(entry.getKey() +
"-" + entry.getValue()));
}
private static void singleSetStream() {
ArrayList<String> list = new ArrayList<>();
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("马尔扎哈");
list.add("欧阳娜娜");
// 可以使用Collection接口中的默认方法stream()生成流
// default Stream<E> stream()
System.out.println("获取单列集合的Stream流");
list.stream().forEach((String s) -> {
System.out.println(s);
});
}
}
17.7.3 Stream流 - 中间方法
特点:返回了 Stream
流对象,用以继续调用方法进行操作流对象
Stream<T> filter(Predicate predicate)
:用于对流中的数据进行过滤Predicate
接口中的方法 :boolean test(T t)
:对给定的参数进行判断,返回一个布尔值
Stream<T> limit(long maxSize)
:截取指定参数个数的数据Stream<T> skip(long n)
:跳过指定参数个数的数据static <T> Stream<T> concat(Stream a, Stream b)
:合并a和b两个流为一个流Stream<T> distinct()
:去除流中重复的元素。依赖(hashCode和equals方法)Stream<T> sorted ()
: 将流中元素按照自然排序的规则排序Stream<T> sorted (Comparator<? super T> comparator)
: 将流中元素按照自定义比较器规则排序
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* @author Carl Zhang
* @description Stream流的中间方法
* @date 2022/1/2 19:12
*/
@SuppressWarnings("ALL")
public class StreamCentreMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张翠山");
list.add("张三丰");
list.add("谢广坤");
list.add("赵四");
list.add("刘能");
list.add("小沈阳");
list.add("张良");
list.add("张良");
list.add("张良");
list.add(new String("张良"));
//1 Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
// Predicate函数式接口的方法 : boolean test(T t):对给定的参数进行判断,返回一个布尔值
// T 是泛型,Stream流里的元素类型
// 返回true就留下,false就过滤掉
//打印集合中三个字名字的元素
//list.stream().filter(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.length() == 3;
// }
//}).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
//2 Stream<T> limit(long maxSize):截取指定参数个数的数据
//获取前两个元素
Stream<String> stream = list.stream();
stream.limit(2).forEach(s -> System.out.println(s));
//3 Stream<T> skip(long n):跳过指定参数个数的数据
//跳过前两个元素,打印后面的元素
//异常IllegalStateException:stream has already been operated upon or closed
//stream.skip(2).forEach(s -> System.out.println(s));
list.stream().skip(2).forEach(s -> System.out.println(s));
//4 static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
ArrayList<String> list2 = new ArrayList<>();
list2.add("迪丽热巴");
list2.add("古力娜扎");
list2.add("欧阳娜娜");
list2.add("马尔扎哈");
Stream.concat(list.stream(), list2.stream()).forEach(s -> System.out.println(s));
//5 Stream<T> distinct():去除流中重复的元素。依赖(hashCode()和equals())
list.stream().distinct().forEach(s -> System.out.println(s));
//这里只剩一个"张良",因为String里的方法根据value的值来返回hashCode
//new String("张良").hashCode();
//6 Stream<T> sorted () : 将流中元素按照自然排序的规则排序
list.stream().sorted().forEach(s -> System.out.println(s));
//7 Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
list.stream().sorted(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
}).forEach(s -> System.out.println(s));
System.out.println();
list.stream().sorted((s1, s2) -> s2.length() - s1.length()).forEach(
s -> System.out.println(s)
);
}
}
17.7.4 Stream流 - 终结方法
特点:空返回值,不能继续调用方法进行操作流对象
void forEach(Consumer action)
:对此流的每个元素执行操作Consumer
接口中的方法void accept(T t)
:对给定的参数执行此操作
long count()
:返回此流中的元素数
import java.util.ArrayList;
import java.util.function.Consumer;
/**
* @author Carl Zhang
* @description Stream流的终结方法
* @date 2022/1/2 19:48
*/
public class StreamEndMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张翠山");
list.add("张三丰");
list.add("谢广坤");
//1 void forEach(Consumer action):对此流的每个元素执行操作
// Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
// 把list集合中的元素放在stream流中
// forEach方法会循环遍历流中的数据
// 并循环调用accept方法 , 把数据传给s
// 所以s就代表的是流中的每一个数据
// 我们只要在accept方法中对数据做业务逻辑处理即可
list.stream().forEach(s -> System.out.println(s));
//2. long count():返回此流中的元素数
//结果:4
System.out.println(list.stream().count());
}
}
17.8 Stream流的收集方法
17.8.1 使用收集方法的原因
问题:使用 Stream
流的方式操作完毕之后,我想把流中的数据起来,该怎么办呢?
解决:引出收集方法
package com.heima.stream;
import java.util.ArrayList;
/**
* @author Carl Zhang
* @description 使用收集方法的原因
* @date 2022/1/2 20:04
* 需求:过滤元素并遍历集合
* 定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
* 将集合中的奇数删除,只保留偶数。
* 遍历集合得到2,4,6,8,10
*/
public class CollectionMethod01 {
public static void main(String[] args) {
//JDK9 新特性 直接传入一个不可变集合的元素,来创建新集合
//ArrayList<Integer> list = new ArrayList<>(List.of(1,2,3,4,5,6,7,8,9,10));
ArrayList<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//使用Stream流过滤掉集合中的奇数,获取偶数
list.stream().filter(i -> i % 2 == 0).forEach(i -> System.out.println(i));
//结论:list集合里的数未改变
//如果需要保留流里面过滤后的元素 -> 使用收集方法
System.out.println(list);
}
}
17.8.2 收集方法介绍
Stream
流的收集方法
-
R collect(Collector collector)
: 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数
收集方法也可以看作一种终结方法,调用完 collect()
不返回 Stream
对象,不可再对流进行操作
17.8.3 三种收集方式
工具类 Collectors
提供了具体的收集方式
public static <T> Collector toList()
:把元素收集到List集合中-
public static <T> Collector toSet()
:把元素收集到Set集合中
package com.heima.stream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
* @author Carl Zhang
* @description Stream流的收集方法
* @date 2022/1/2 20:22
* 需求 :
* 定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
* 将集合中的奇数删除,只保留偶数。
* 遍历集合得到2,4,6,8,10
*/
public class CollectionMethod02 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//通过收集方法获取过滤后的元素
//解析:
//1. R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数
//2. 通过工具类Collector里的规则来将收集到的元素保存到集合里
// public static <T> Collector toList():把元素收集到List集合中
List<Integer> list1 = list.stream().filter(i -> i % 2 == 0).collect(
Collectors.toList());
System.out.println(list1); //[2, 4, 6, 8, 10]
//将过滤好的元素收集到Set集合里
//public static <T> Collector toSet():把元素收集到Set集合中
Set<Integer> set = list.stream().filter(i -> i % 2 == 0).collect(
Collectors.toSet()
);
System.out.println(set); //[2, 4, 6, 8, 10]
}
}
public static Collector toMap(Function keyMapper,Function valueMapper)
:把元素收集到Map集合中
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Carl Zhang
* @description 将过滤好的元素收集到Map集合里
* @date 2022/1/2 20:33
* public static Collector toMap(Function keyMapper,Function valueMapper):
* 把元素收集到Map集合中
* <p>
* 1 创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
* "zhangsan,23"
* "lisi,24"
* "wangwu,25"
* 2 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
*/
public class CollectionMethod03 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan,23");
list.add("lisi,24");
list.add("wangwu,25");
//筛选出24岁以上的元素
Stream<String> stream = list.stream().filter(s -> {
String[] split = s.split(",");
//获取年龄
int age = Integer.parseInt(split[1]);
//筛选出大于等于24岁的
return age >= 24;
});
//将过滤好的元素收集到Map集合
//public static Collector toMap(Function keyMapper,Function valueMapper):
Map<String, String> collect = stream.collect(Collectors.toMap(
// 获取键:Function keyMapper -- 传入一个函数式接口的实现类
// s 表示流里的元素
// 获取第一个元素,做为键
s -> s.split(",")[0],
// 获取值Function valueMapper)
s -> s.split(",")[1]
));
//遍历
Set<Map.Entry<String, String>> entries = collect.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + "-" + entry.getValue());
}
}
}
17.9 Base64(编码表) [了解]
17.9.1 Base64 介绍
编码表 :定义了字符和数字的映射关系
Base64
编码是一种常用的字符编码,在很多地方都会用到,他核心作用是保证传输数据的正确性,有些系统只能使用 ASCII
字符(本文字符) , Base64
就是用来将非**ASCII**
** 字符的数据转换成**ASCII**
字符**的一种方法,而且 base64
特别适合在后面学习到的 http
, mime
协议下快速传输数据。
-
MIME
: 是描述消息内容类型的因特网标准。消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。 -
base64
不是安全领域下的加密解密算法。能起到安全作用的效果很差,而且很容易破解。- 只是对数据的编解码 , 不是加密解密算法
-
Base64
编码表由以下64个字符组成部分 :- 26个英文字母包含大小写 :52个
- 0~9 : 10个
-
- / : 2
- 如何将普通的数据转换为
Base64
字符数据。
编码的规则:把3个字节变成4个字节。
原始数据 : 3 x 8bit = 24 bit
目标数据 : 24 / 4bit = 6 bit
例如 :
转换前 11111111, 11111111, 11111111 (二进制)
转换后 00111111, 00111111, 00111111, 00111111 (二进制)
一个字节代表的范围 :
最小的字节是 0000 0000 -> 对应Base64是 0 编号 -> A
最大的字节是 0011 1111 -> 对应Base64是 63编号 -> /
所以一个字节可以代表的是Base64所有的字符
解码的规则 :
编码的规则反过来 (把0去掉,所有二进制加在一起除以三 , 就可以恢复三个字节)
- 编码 : 如何将普通的数据编码成为Base64数据
- 把三个字节变成四个字节
- 解码 : 如何将
Base64
数据解码成为普通数据- 把四个字节在恢复三个字节
- 注意 : 这个普通数据可以是 : 文本,音频,视频,可以是任何的数据。
17.9.2 Base64 API
在 JDK8
版本中,Base64
编码已经成为 Java
类库的标准。 内置了 Base64
编码的编码器和解码器。
java.util.Base64
工具类中存在两个静态内部类:
| 序号 | 内嵌类 & 描述 |
| --- | --- |
| 1 | static class Base64.Decoder该类实现一个解码器,使用 Base64来解码字节数据。 |
| 2 | static class Base64.Encoder该类实现一个编码器,使用 Base64来编码字节数据 |
- 编码器:
**Encoder**
** **
目的:就是将普通的字节数据变成Base64
的数据
byte[] encode(byte[] src)
使用Base64将指定字节数组中的所有字节 编码为新分配的字节数组。
String encodeToString(byte[] src)
使用Base64将指定的字节数组,编码为字符串。
OutputStream wrap(OutputStream os)
使用Base64将参数中字节流中的数据,编码为base64格式的字节流
- 解码器:
**Decoder**
** **
目的 :将Base64
的数据变成普通正常的数据
byte[] decode(byte[] src)
使用Base64从输入字节数组中解码所有字节,将结果写入新分配的字节数组。
byte[] decode(String src)
使用Base64将Base64编码的字符串,解码为新分配的字节数组。
InputStream wrap(InputStream is)
用于解码Base64编码字节流 , 返回一个输入流
17.9.3 Basic 编码器
- 使用的字符A-Za-z0-9+/ 编解码
A-Za-z0-9+/
是标准的 BASE64
编码,用于处理常规的需求 (编码少量数据)
static Base64.Decoder getDecoder()
返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。
static Base64.Encoder getEncoder()
返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
- 【代码实例】
package com.heima.base64;
import java.util.Base64;
/**
* @author Carl Zhang
* @description
* 小明要给小红发送一句话,这句话使用Base64编码
* 小红解码查看消息
* @date 2022/1/9 17:15
*/
public class Base6401 {
public static void main(String[] args) {
//编码:
//获取base64的编码器
Base64.Encoder encoder = Base64.getEncoder();
//将字符串转成字节数组
byte[] bytes = "hello, world".getBytes();
//调用方法把字符串编码成base64编码的字符串
String base64String = encoder.encodeToString(bytes);
System.out.println("小明:" + base64String); //aGVsbG8sIHdvcmxk
//解码
//获取base64的解码器
Base64.Decoder decoder = Base64.getDecoder();
//调用方法对base64编码的字符串进行解码,获取普通编码的字节数组
byte[] decode = decoder.decode(base64String);
//把字节数组转换成字符串打印
System.out.println("小红:" + new String(decode)); //hello, world
}
}
17.9.4 URL 编码器
专门对URL进行编解码
- 使用的字符 A-Za-z0-9 -_ 编解码
- 一般用于对网址进行编解码
A-Za-z0-9-_
Base64
的静态方法:
static Base64.Decoder getUrlDecoder()
返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。
static Base64.Encoder getUrlEncoder()
返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。
【代码实例】
import java.util.Base64;
/**
* @author Carl Zhang
* @description URL编码
* 小明给小灰发送了一个网址,使用Base64编码了。
* 小灰解码看网址
* @date 2022/1/9 17:29
*/
public class Base6402 {
public static void main(String[] args) {
//编码
//获取Url编码器
Base64.Encoder urlEncoder = Base64.getUrlEncoder();
//将字符串转成字节数组
byte[] simpleUrl = "https://820.workarea7.live/index.php".getBytes();
//调用方法对字节数组编码成base64编码的字符串
String base64Url = urlEncoder.encodeToString(simpleUrl);
System.out.println("url编码 = " + base64Url); //aHR0cHM6Ly84MjAud29ya2FyZWE3LmxpdmUvaW5kZXgucGhw
//解码
//获取Url解码器
Base64.Decoder urlDecoder = Base64.getUrlDecoder();
//将Base64的字符串解码成普通编码的字节数组
byte[] decode = urlDecoder.decode(base64Url);
//将字节数组转换成字符串并打印
System.out.println("url解码 = " + new String(decode)); //https://820.workarea7.live/index.php
}
}
17.9.5 MIME 编码器
- 使用的字符 A-Za-z0-9+/ 编解码
- 并且对
MIME
格式友好 :每一行输出不超过76个字符 ,而且每行以“\r\n”符结束
static Base64.Encoder getMimeEncoder()
返回一个Base64.Encoder编码使用MIME型base64编码方案。
static Base64.Decoder getMimeDecoder()
返回一个Base64.Decoder解码使用MIME型BASE64解码方案。
- 如果需要将字节流包装成为具有编码解码的能力,分别使用
Encoder
,Decoder
的wrap
方法,如下:
Encoder:
OutputStream wrap(OutputStream os) 使用Base64编码方案包装用于编码字节数据的输出流。
Decoder:
InputStream wrap(InputStream is) 返回一个输入流,用于解码Base64编码字节流。
【代码实例】
- 需求:设计两个方法分别实现对文件的编码和解码
import java.io.*;
import java.util.Base64;
/**
* @author Carl Zhang
* @description MIME编码解码
* @date 2022/1/9 17:48
*/
public class Base6403 {
public static void main(String[] args) throws IOException {
//调用方法对文件进行编码
encodeFile("img\\测试图片.png", "img\\编码后的测试图片.txt", Base64.getMimeEncoder());
//调用方法对编码后的文件进行解码
decodeFile("img\\编码后的测试图片.txt", "img\\解码后的测试图片.png", Base64.getMimeDecoder());
}
/**
* 实现对文件进行Base64编码得到新文件
*
* @param src 代表的是源文件的路径
* @param dest 代表的是目标文件的路径
* @param encoder 编码器对象
* @throws IOException
*/
public static void encodeFile(String src, String dest, Base64.Encoder encoder) throws IOException {
// 把普通的字节输出流封装成base64字节输出流
OutputStream fos = encoder.wrap(new FileOutputStream(dest));
// 创建一个普通的字节输入流
FileInputStream fis = new FileInputStream(src);
byte[] bytes = new byte[1024];
int len;
// 读取原始的文件数据
while ((len = fis.read(bytes)) != -1) {
// 写出去的是Base64数据
fos.write(bytes, 0, len);
}
// 释放资源
fis.close();
fos.close();
}
/**
* 实现对Base64编码文件,解码得到源文件
*
* @param src 代表的是源文件的路径
* @param dest 代表的是目标文件的路径
* @param decoder 编码器对象
* @throws IOException
*/
public static void decodeFile(String src, String dest, Base64.Decoder decoder) throws IOException {
// 把普通的字节输入流 , 封装成base64字节输入流
InputStream fis = decoder.wrap(new FileInputStream(src));
//将普通的字节输入流,包装成为一个能够解码Base64数据的输入流
OutputStream fos = new FileOutputStream(dest);
byte[] bytes = new byte[1024];
int len;
//按照Base64的解码方案进行读取
while ((len = fis.read(bytes)) != -1) {
//将正常的数据写到文件中
fos.write(bytes, 0, len);
}
// 释放资源
fis.close();
fos.close();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南