编译软件:IntelliJ IDEA 2019.2.4 x64
运行环境:win10 家庭中文版
jdk版本:1.8.0_361
目录
一. 函数式编程思想
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。编程中的函数,也有类以的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。
对于调用者来做,关注汶个方法具备什么样的功能。相对而言,面向对象过分强调"必须涌过对象的形式来做事情",而函数式思想则尽量忽略面向对象的复杂语法一一强调做什么,而不是以什么形式做。
面向对象的思想:
做一件事情,找一个能解决这个事情的对象调用对象的方法,完成事情
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
二. 函数式接口
函数式接口是指一种特殊的接口,SAM接口 (Single Abstract Method) 只有唯一抽象方法的接口。
说明: 抽象只有唯一的一个,但是对于非抽象方法不限制。
函数式接口的概念时JDK1.8引入的,然后它除了要求接口是SAM特征之外,还强烈建议接口的声明上方加一个注解:
@FunctionalInterface 函数式接口的注解
回忆之前学过的接口中,哪些接口符合SAM接口特征的?
(1)java.util.Comparator int compare(T t1 ,T t2)
(2)java.lang.Comparable int compareTo(T t)
(3)java.lang.Runnable void run()
(4)java.util.function.Predicate boolean test()
(5)java.lang.Iterable<>接口 Iterator iterator()
(6)java.io.FileFilter boolean accept(File pathname)
java.io.Serializable接口,不是,没有抽象方法
java.Lang.CLoneabLe接口,不是,没有抽象方法
java.util.Iterator接口,不是,有两个抽象方法boolean hasNext()
E next)
java.util.Collection、List、Set、Oueue、Deque、Map<K,V>,不是,有很多抽象方法
上面这些SAM接口中,哪些有@FunctionalInterface注解:
(1)java.util.Comparator int compare(T t1 ,T t2)(2)java.lang.Runnable void run() boolean test() (这个其实也是新的)
(3)java.util.function .Predicate
(4) java.io.FileFilter boolean accept(File pathname)
Java8在java.util.function包中新增了很多很多的函数式接口。
三. 新版函数式接口的四大经典代表
(1) 消费型接口代表
Consumer
抽象方法: void accept(T t)
特点: 有一个参数,返回值是void,表示无返回值。
调用这个抽象方法时,相当于你要给它一个实参,但是得不到返回值,相当于 有去无回,纯消费行为。
(2) 供给型接口代表/泰献型接口代表
Supplier
抽象方法 T get()
特点:没有参数,有返回值
调用这个抽象方法时,相当于你不用给它传参数,却可以得到一个返回值,相当于 空手套白狼。
(3)判断型接口/断定型接口代表
Predicate
抽 象方法 boolean test(T t)
特点: 有一个参数,返回值类型是固定的boolean
调用这个抽象方法时,相当于你给它一个参数之后,它会告诉你这个参数是否满足xx条件,满足就返回true,否则就返回false
(4)功能型接口代表
Function<T,R>
抽象方法 RLapply(T t)
特点是:有一个参数,有一个返回值
调用这个抽象方法时,相当于你要给它一个实参,同时也 可以得到一个返回值,相当于礼尚往来
四. Lambda 表达式
4.1 Lambda表达式的作用
给函数式接口的变量或形参赋值用的。传递一段代码这段代码本质上就是西数式接口的抽象方法的方法体。
4.2 语法格式
(形参列表) ->{Lambda体)
(形参列表) : 是函数式接口的抽象方法的形参列表
->: 称为Lambda操作符,中间不能有空格
{Lambda体: 就是函数式接口的抽象方法的方法体
代码演示如下
//为何不用JUite的test方法去测试,该方法有一个缺点,在其内部使用多线程,有可能test里的线程还没启动就被test干掉了
//相比而言,main方法会等待里面的线程全部执行完,相比较而言,main方法比较保险
public static void main(String[] args) {
//使用lambda表达式对Runnable接口的变量赋值。要实现打印一句话: hello lambda
//Runnable接口的抽象方法: void run()
Runnable r= () -> {
System.out.println("hello lambda"); //里面的;是输出语句的
}; //外面的;是 赋值语句 + lambda表达式的
new Thread(r).start(); //完全等价于下面的代码
System.out.println("-----------------");
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello lambda");
}
}).start();
}
4.3 Lambda表达式在某些情况下,可以简化
(1)当{Lambda体}里面只有一个语句时,那么可以省略 {},同时省略里面的;
代码演示如下:
Runnable r= () -> {
System.out.println("hello lambda"); //里面的;是输出语句的
}; //外面的;是 赋值语句 + lambda表达式的
new Thread(r).start();
@Test
public void test01(){
//精简如下
Runnable r= () -> System.out.println("hello lambda");
new Thread(r).start();
}
(2)当Lambda体)里面只有一个语句时,那么可以省略,同时省略里面的;如果此时这个语句是一个return语句,那么要连同return省掉
代码演示如下:
//案例:在一个全是字符串的集合中,删除 包含“o”字母的单词
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("atguigu");
list.add("java");
list.add("bigdata");
//删除 包含“o”字母的单词
//匿名类的形式
list.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("o");
}
});
System.out.println(list);
//从匿名类转换为lambda表达式
list.removeIf(
(String s) -> {
return s.contains("o");
}
);
//上面的lambda式子精简如下
list.removeIf(
(String s) -> s.contains("o")
);
System.out.println(list);
(3)当(形参列表)的形参类型是已知的,或者可以自动推断的,那么形参列表的类型可以省略
代码演示如下:
//接上面的案例
//精简如下
list.removeIf(
(s) -> s.contains("o")
);
💡如果此时省略了形参的数据类型之后,只剩下一个形参,它是这样的(形参名),那么此时()也可以省略
代码演示如下:
//再精简lambda表达式
list.removeIf(
s -> s.contains("o")
);
🔔说明:
如果形参列表是(),()不能省略
如果形参不止一个,()也不能省略
如果Lambda体中不止一个语句等也不能省略。
代码演示如下:
@Test
public void test03(){
String[] arr = {"hello","Bob","Rose","java","chai"};
//使用匿名类
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
}
);
//使用lambda表达式
Arrays.sort(arr, (o1,o2) -> o1.compareToIgnoreCase(o2) );
}
五. 消费型接口与lambda 表达式
消费型接口Consumer抽象方法:
void accept(T t)
有参无返回值
例子:
JDK1.8 java.Lang.terable接口增加了一个默认方法,
public default void forEach(Consumer<? super T> action)
Iterable接口增加了方法,意味着所有的CoLLection系列的集合都有这个方法。因为ColLection接口继承了Iterable接口
代码演示如下:
@Test
public void test04(){
ArrayList<String> list = new ArrayList<>();list.add("hello");
list.add("world");
list.add("atguigu");
list.add("java");
list .add("bigdata");
//forEach方法的功能是,遍历集合,并对集合的每一个元素做xx事情,具体做什么事情
// 由Consumer接口的accept抽象方法决定
//使用匿名内部类的形式
/*list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
*/
//使用lambda表达式
list.forEach((s) -> System.out.println(s) );
}
六. 供给型接口与lambda表达式
供给型接口Supplier 抽象方法:
T get() 无参有返回值
例如: 使用Lambda表达式给一个Supplier类型的变量赋值一个“轨道”
代码演示如下:
@Test
public void test05(){
Supplier<String> s= ()-> {return "轨道";};
//镜检如下
Supplier<String> str= ()-> "轨道";
methiod(s);
//注意以下,在方法()中并非为lambda式的精简缩写,而是str的替换
methiod(()-> "轨道");
}
//泛型方法
public <T> void methiod(Supplier<T> s){
System.out.println(s.get());
}
七. 判断型接口与lambda表达式
案例:在一个全是字符串的集合中,删除 包含“o”字母的单词
代码演示如下:
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("atguigu");
list.add("java");
list.add("bigdata");
//删除 包含“o”字母的单词
//匿名类内部类的形式
list.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("o");
}
});
//精简如下
list.removeIf(
s -> s.contains("o")
);
System.out.println(list);
八. 功能型接口与lambda表达式
案例一:使用Lambda表达式,给一个Function接口的变量赋值完成给某个字符串的首字母转为大写。
思路:
把字符串的首字母变为大写怎么实现?
(1) 把字符串的首字母拿出来,转为大写 (2) 把转换后的首字母 拼接上原来字符串除了首字母的部分,例如"heLlo"
把h拿出来,转为H,然后用H 与eLLo"拼接
Function<T,R> 抽象方法 R apply(T t) 有参有返回值
代码演示如下:
@Test
public void test06(){
//某个字符串的首字母转为大写
//Function<T,R> T是指定形参类型,R是指定返回值类型
Function<String,String> f= s -> Character.toUpperCase(s.charAt(0))+s.substring(1);
System.out.println(f.apply("hello"));
}
JDK1.8中Map接口新增了一个方法:
default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
BiFunction<T,U,R>函数式接口,它的抽象方法 R apply(T t,u u)有2个参数,一个返回值。
案例二:将所有的value转换为大写
代码演示如下:
@Test
public void test07(){
//将所有的value转换为大写
HashMap<Integer,String> map=new HashMap<>();
map.put(1,"jack");
map.put(2,"hello");
map.replaceAll((key,value) -> value.toUpperCase());
System.out.println(map);
}
九. lambda 表达式综合案例
9.1 案例一
创建一个ArrayList,并添加26个小写字母到List中,并使用forEach遍历输出
方法思路:
第一步:
搞清楚forEach 方法的方法签名
方法 = 方法头 + 方法体,方法头又被称为方法签名。
方法头:[修饰符] 返回值类型
方法名([形参列表] )[throws 异常列表] 方法体:[语句代码}
void forEach(Consumerk? super E> action)搞清楚Consumer接口的抽象方法,它是一个函数式接口,就可以使用Lambda表达式
第二步:
抽象方法: void accept(T t) 【有参无返回值】
对于函数式接口的抽象方法来说,用Lambda表达式给他赋值时,不关心方法名,关心形参列表和返回值类型。
代码演示如下:
@Test
public void test08(){
ArrayList<Character> list=new ArrayList<>();
for (char i = 'a'; i <='z' ; i++) {
list.add(i);
}
System.out.println(list);
System.out.println("------------------");
list.forEach(c -> System.out.print(c+"\t"));
}
9.2 案例二
创建一个HashMap,并添加如下编程语言排名和语言名称到map中,并使用forEach遍历输出
排名 | 语言 |
---|---|
1 | Java |
2 | c |
3 | python |
4 | c++ |
5 | c# |
代码演示如下:
@Test
public void test09(){
HashMap<Integer,String> map=new HashMap<>();
map.put(1,"java");
map.put(2,"c");
map.put(3,"python");
map.put(4,"c++");
map.put(5,"c#");
map.forEach((key,value) -> System.out.println(key+":"+value));
}
9.3 案例三
使用Lambda表达式给Supplier接口的变量赋值,实现产生1个100以内的整数功能。
代码演示如下:
@Test
public void test10(){
//使用Lambda表达式给Supplier接口的变量赋值,实现产生1个100以内的整数功能。
Supplier<Integer> s= ()-> new Random().nextInt(100);
System.out.println(s);//打印s是得不到这个随机数值的
System.out.println(s.get());//通过调用s.get()得到随机数值
}
9.4 案例四
声明一个Employee员工类型,包含属性编号、姓名、薪资,属性私有化,提供有参构造,get/set,重写toString.
添加n个员工对象到一个HashMap<lnteger,Employee>集合中,其中员工编号为key,员工对象为value。
调用Map的forEach遍历集合
调用Map的replaceAll方法,将其中薪资低于10000元的,薪资设置为10000。
再次调用Map的forEach遍历集合查看结果
代码演示如下:
public class Employee {
private int id;
private String name;
private int salary;
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public Employee(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
}
@Test
public void test11(){
HashMap<Integer,Employee> map=new HashMap<>();
Employee e1=new Employee(1,"张三",6700);
Employee e2=new Employee(2,"李四",9700);
Employee e3=new Employee(3,"王五",16000);
Employee e4=new Employee(4,"赵六",27000);
map.put(e1.getId(),e1);
map.put(e2.getId(),e2);
map.put(e3.getId(),e3);
map.put(e4.getId(),e4);
map.forEach((key,value)-> System.out.println(map.get(key)));
System.out.println("--------------------------------------");
map.replaceAll((key,value)-> {
if (map.get(key).getSalary()<10000){
map.get(key).setSalary(10000);
}
return value;
});
map.forEach((key,value)-> System.out.println(map.get(key)));
}
十. 方法引用与构造器引用
10.1 方法引用
10.1.1 什么是方法引用?
它是指一种新的语法格式:
类名 :: 方法名
对象名 :: 方法名
10.1.2 方法引用用在哪里?
作用: 方法引用是用于简化Lambda表达式的语法。
10.1.3 方法引用可使用的情况
注意:
不是所有的Lambda表达式都可以用方法引用进行简化的,只有满足以下情况时,才能用它进行简化:
1)Lambda体只有一个语句,并且这个语句是通过调用一个现有的类或对象的方法来完成的。
并且在调用方法时,所使用的实参正好时Lambda表达式的形参,整个使用过程中,没有额外的数据出现。
代码演示如下:
@Test
public void test12(){
ArrayList<String> list=new ArrayList<>();
list.add("jack");
list.add("hello");
list.add("rose");
list.add("tom");
list.forEach(t -> System.out.println(t));//使用lambda进行遍历,可进行方法引用以简化
list.forEach(t -> System.out.println(t+"\t"));//不能根据这个lambda进行方法引用
System.out.println("--------------------------");
list.forEach(System.out:: println );//使用方法引用
}
(2) Lambda体只有一个语句,并且这个语句是通过调用一个现有的对象的方法来完成的。
此时调用方法的对象是Lambda表达式的第一个形参,并且Lambda表达式的剩下的形参,正好作为该方法的实参,
整个使用过程中,没有额外的数据出现。
代码演示如下:
@Test
public void test13(){
String[] arr = {"hello","Bob","Rose","java","chai"};
Arrays.sort(arr, (o1,o2) -> o1.compareToIgnoreCase(o2) );
for (String s : arr) {
System.out.println(s);
}
System.out.println("-----------------------");
String[] arr1 = {"hello","Bob","Rose","java","chai"};
Arrays.sort(arr1, String::compareToIgnoreCase );
for (String s : arr) {
System.out.println(s);
}
}
10.2 构造器引用
当Lambda表达式是一个创建对象的表达式,即Lambda体是一个new表达式,而且 所有构造器的实参,都是Lambda表达式的形参,那么此时就可以使用构造器引用。
语法格式:
类名 :: new
代码演示如下:
@Test
public void test14(){
/* 功能型接口Function<T,R>,有一个抽象方法 R apply(T t)
希望它通过某个字符串,作为Person对象的name值,创建一个Person对象*/
// Function<String,Person> fun= s -> new Person(s);
Function<String,Person> fun= Person::new;
String[] names={"jack","rose","tom"};
//根据names的字符串,创建一个Person数组
Person[] people=new Person[names.length];
for (int i = 0; i < names.length ; i++) {
people[i]=fun.apply(names[i]);
}
for (Person person : people) {
System.out.println(person);
}
}
10.3 数组引用
当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参正好是给创建这个数组对象的长度,就可以数组构造引用
语法格式:
数组类型名: :new
代码演示如下:
@Test
public void test15(){
/* 如果使用Lambda表达式给一个Function接口的变量赋值
需求: 给定一个长度,你返回一个对应长度的String[]数组给我*
功能型接口Function<T,R> 有一个抽象方法 R apply(T t)
形参的类型: Integer
返回值类型: String[]*/
// Function<Integer,String[]> fun=l -> new String[l];
Function<Integer,String[]> fun= String[]::new;
String[] str=fun.apply(5);
for (String s : str) {
System.out.print(s+"\t");
}
}
十一. Stream API
11.1 stream是什么?
stream这个单词,在之前IO流的文章就提到过。在在IO流中是代表一个字节输入流或一个字节输出流,它是用于数据的传输的今天讲的这个stream也是代表一个数据流,但是这个数据流和之前的IO流是有区别的;
今天说的stream它是指用于数据“加工”的一套流程。例如:数据的过滤、数据的统计、数据的迭代、数据的修改、删除、查询筛选等
stream本身是不存储数据的,存储数据的是数组、集合这样的容器。
11.2 特点
(1) stream本身是不存储数据的
(2) stream每一次加工处理都会产生一个新的stream对象
代码演示如下:
@Test
public void test16(){
ArrayList<String> list=new ArrayList<>();
list.add("jack");
list.add("option");
list.add("java");
list.add("world");
list.add("h5");
//1)创建stream
//通过集合创建Stream
Stream<String> stream = list.stream();
/* stream.filter(new Predicate<String>() { //Predicate接口为判断式,可使用lambda
@Override
public boolean test(String s) {
return s.contains("o");
}
});*/
//2)中间加工处理
//筛选含”o“的单词
//lambda表达式
Stream<String> o = stream.filter(s -> s.contains("o"));//原先得Steream流stream本身不会存储任何数据且每一次加工处理都会产生一个新的Stream流对象
//3)终结
//遍历流中剩余的元素
// o.forEach(s -> System.out.println(s));
o.forEach(System.out::println);
System.out.println("list:"+list);//list:[jack, option, java, world, h5] 不会改变源数据
}
(3) stream的中间处理/加工操作会被延迟,一直要到最后取结果的“终结作”才会执行。
不加”终结操作“前后对比:
没加代码演示如下:
@Test
public void test17(){
ArrayList<Student> list=new ArrayList<>();
list.add(new Student("KFC"));
list.add(new Student("dragon"));
//1)创建流对象
Stream<Student> stream = list.stream();
//2)加工处理
//peek():peek(Consumer<? super T> action)
//返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的操作。
Stream<Student> peek = stream.peek(System.out::println);
//3)终结操作
//3)没写得时间,前面得代码都没执行
/*
long nums=peek.count();//统计流中的元素的个数
//peek.count()有返回值,不是stream,所以它是一个终结操作
System.out.println(nums);
*/
}
加了 代码演示如下:
@Test
public void test17(){
ArrayList<Student> list=new ArrayList<>();
list.add(new Student("KFC"));
list.add(new Student("dragon"));
//1)创建流对象
Stream<Student> stream = list.stream();
//2)加工处理
//peek():peek(Consumer<? super T> action)
//返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的操作。
Stream<Student> peek = stream.peek(System.out::println);
//3)终结操作
//3)没写得时间,前面得代码都没执行
long nums=peek.count();//统计流中的元素的个数
//peek.count()有返回值,不是stream,所以它是一个终结操作
System.out.println(nums);
}
(4) stream的加工处理不会改变的数据源(集合和数组的元素,个数等)
11.3 如何使用stream?
(1) 创建Stream
创建Stream的几种方法:
①通过集合对象.stream
JDK1.8在
ColLection系列集合中增加了方法:default Stream stream()
②通过数组工具类Arrays.stream方法
代码演示如下:
@Test
public void test18(){
//通过Arrays。stream()创建
int[] a={1,2,4,5,10};
IntStream stream = Arrays.stream(a);
}
③Stream接口中有这样的一些静态方法,可以创建
Streamof(T… value) : 有限流
static Stream generate(Supplier s) : 无限流
static Stream iterate(T seed, UnaryOperator f):无限流
代码演示如下:
@Test
public void test19(){
Stream<String> jack = Stream.of("jack", "hello", "world");
Stream<Integer> integerStream = Stream.of(1, 3, 5, 2);
}
@Test
public void test20(){
//static <T> Stream<T> generate(Supplier<T> s)
// 返回无限顺序无序流,其中每个元素由提供的Supplier 。 这适合于产生恒定流,随机元素流
Stream<Double> generate = Stream.generate(Math::random);
generate.forEach(s -> System.out.println(s));
}
@Test
public void test21(){
/* static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
返回有序无限连续Stream由函数的迭代应用产生f至初始元素seed ,产生Stream包括seed , f(seed) , f(f(seed)) ,等
第一元件(位置0在) Stream将是提供seed 。 对于n > 0 ,位置n的元素将是将函数f应用于位置n - 1的元素的n - 1 。
参数类型
T - 流元素的类型
参数
seed - 初始元素
f - 要应用于前一个元素以生成新元素的函数
结果
一个新的顺序 Stream*/
/*
Function<T,R>接口抽象方法 R apply(T t);参数的类型和返回值的类型可以不同
UnaryOperator<T> 接口抽象方法 T apply(T t)参数的类型和返回值的是一样的
*/
//需求:对每一个元素,进行迭代处理,从第一个元素开始,每次迭代都+2,然后一直在上一次的基础上,不断迭代。
Stream<Integer> iterate = Stream.iterate(1, t -> t + 2);
iterate=iterate.peek(t->{ //让流中的数据每隔100ms产生一个
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
iterate.forEach(t-> System.out.println(t));
}
(2) 中间加工处理
处理方法如下:
代码演示如下:
@Test
public void test22(){
//过滤所有的偶数
Stream.of(1,2,3,4,5) //创建流
.filter(t -> t%2==0) //中间加工处理
.forEach(t -> System.out.println(t)); //终结操作
}
@Test
public void test23(){
//去掉所有的重复数字
Stream.of(1,2,3,4,5,2,4,6,5,3,1) //创建流
.distinct() //中间加工处理
.forEach(t-> System.out.println(t)); //终结操作
}
@Test
public void test23(){
//去掉所有的重复数字
Stream.of(1,2,3,4,5,2,4,6,5,3,1) //创建流
.distinct() //中间加工处理
.forEach(t-> System.out.print(t+"\t")); //终结操作
}
@Test
public void test24(){
//去掉所有的重复数字 且 只留偶数
//中间加工处理绝不仅仅只能写一步,它可以写很多步
Stream.of(1,2,3,4,5,2,4,6,5,3,1) //创建流
.distinct() //中间加工处理
.filter(t-> t%2==0) //中间加工处理
.forEach(t-> System.out.print(t+"\t")); //终结操作
}
@Test
public void test25(){
//在初始元素为1,元素累加2的无限流中,取前10个
Stream.iterate(1,t->t+2)
.limit(10)//取无限流中前10个
.forEach(System.out::println);
}
@Test
public void test26(){
//在初始元素为1,元素累加2的无限流中,跳过前10个,并只取前10个
Stream.iterate(1,t->t+2)
.skip(10)//跳过无限流中前10个
.peek(t->{ //数据每隔200ms出现一次,使之不要出现的那么的快
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
})
.limit(10) //只取前10个
.forEach(System.out::println);
}
@Test
public void test27(){
//统计这组数据中的偶数个数,是不重复的偶数,并且要打印出来
long count=Stream.of(1,2,3,4,5,2,4,6,5,3,1) //创建流
.filter(t -> t%2==0) // 过滤偶数 中间加工处理
.distinct() //去重复
.peek(t-> System.out.print(t+"\t")) //中间加工
.count();
System.out.println();
System.out.println("偶数个数:"+count);
}
@Test
public void test28(){
//从小到大排序
Stream.of(1,5,2,89,45,32,9,12) //创建流
.sorted() //中间加工 排序
.forEach(System.out::println); //终结
}
@Test
public void test29(){
ArrayList<Student> list=new ArrayList<>();
list.add(new Student("kfc"));
list.add(new Student("dragon"));
//按照名字排序
list.stream() //创建流
.sorted((s1,s2)-> s1.getName().compareTo(s2.getName()))//中间加工 排序
.forEach(t -> System.out.println(t)); //终结
}
@Test
public void test30(){
//将原来流中的数据变成它的2倍并打印
Stream.of(1,2,3,4,5)//创建流
.map(t-> t*2) //中间加工
.forEach(System.out::println); //终结
}
@Test
public void test31(){
//取出单词的首字目
Stream.of("hello","world","java")
.map(t -> t.charAt(0) )
.forEach(System.out::println);
}
@Test
public void test32(){
//把字符串每个字符串起来,变成一个新的流
/*
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
Function<TR>是功能型函数式接口 抽象方法 R apply(T t)
T: ? super T
R : ? extends Stream?extends R> 说明function函数使用后,apply方法结果要是一个stream对象
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
T: ? super T
R: ? extends R
*/
Stream.of("hello","world","java")
.flatMap( t-> {
char[] chars = t.toCharArray();
Character[] c = new Character[chars.length];
for (int i = 0; i < chars.length; i++) {
c[i] = chars[i];
}
return Arrays.stream(c);
})
.forEach(System.out::println);
}
(3) 终结操作,它的方法的返回值不再是一个流,而是其他的类型
注意:
中间加工处理的方法返回值结果仍然是stream类型。
终结操作处理的方法返回值结果就不再是stream类型。表示之后无法再进行流处理。
具体的方法:
方法 | 描述 |
---|---|
boolean allMatch(Predicate p) | 检查是否匹配所有元素 |
boolean anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
boolean noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
Optional findFirst() | 返回第一个元素 |
Optional findAny() | 返回当前流中的任意元素 |
long count() | 返回流中元素总数 |
Optional max(Comparator c) | 返回流中最大值 |
Optional min(Comparator c) | 返回流中最小值 |
void forEach(Consumer c) | 迭代 |
T reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
U reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional |
R collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
代码演示如下:
@Test
public void test33(){
//判断是否全部为奇数
boolean b= Stream.of(1,2,3,4,5,2,4,6,5,3,1)//创建流
.allMatch(t -> t%2!=0); //终结
System.out.println("是否全部为奇数:"+b);
}
@Test
public void test34(){
Optional<Integer> first = Stream.of(1, 2, 3, 4, 5, 2, 4, 6, 5, 3, 1)//创建流
.findFirst(); //终结
System.out.println("第一个:"+first);
}
@Test
public void test35(){
//对于稳定的流(元素稳定且固定),findAny() 等价于 findFirst()
Optional<Integer> any = Stream.of(1, 2, 3, 4, 5, 2, 4, 6, 5, 3, 1)//创建流
.findAny(); //终结
System.out.println("第一个:"+any);
}
@Test
public void test36(){
//随机产生10个【0-100】的整数,找出最大值
Optional<Integer> max= Stream.generate(() -> (new Random().nextInt(101)))
.limit(10)
.peek(t-> System.out.print(t+"\t"))
.max(Integer::compareTo);
System.out.println(max);
}
@Test
public void test37(){
//累加和
/*
Optional<T> reduce(BinaryOperator<T> accumulator);
public interface BinaryOperator<T> extends BiFunction<T,T,T>
public interface BiFunction<T,U,R> 的抽象方法 R apply(T t,U u);
BinaryOperator接口的抽象方法 T apply(T t,T u);
*/
Optional<Integer> reduce = Stream.of(1, 2, 3, 4, 5)
.reduce((t1, t2) -> t1 + t2);
System.out.println(reduce);
}
@Test
public void test38(){
/* ColLector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
另外, CoLLectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。*/
ArrayList<String> list=new ArrayList<>();
list.add("jack");
list.add("hello");
list.add("world");
list.add("java");
Set<String> a = list.stream()
.filter(s -> s.contains("a"))
.peek(s -> System.out.println(s))
.collect(Collectors.toSet());
System.out.println("list:"+list);
System.out.println("set:"+a);
}
十二.Optional 类
12.1 为什么要引入Optional类?
原因:
Java很多方法的返回值 (更多是返回值)
或形参是引用数据类型时,返回null。对方拿到这个数据时,没有做非空判断的话,会发生空指针异常。或者说是加非空判断f (xx !=
null)使得代码很臃肿。引入了optional类来尽量的降低空指针异常的风险,又可以简化代码。
12.2 Optional类特点
(1) 它是一个容器
(2) 只包含一个对象的容器
12.3 如何使用它?
12.3.1 创建它
static Optional empty(): 得到一个空的容器对象,里面没有元素,容器对象有。staticOptionalof(T value) : 得到一个包含元素的容器对象,里面一定有元素,而且非null
代码演示如下:
@Test
public void test01(){
Optional<Object> empty = Optional.empty();
System.out.println(empty);
Optional<String> java = Optional.of("java");
System.out.println(java);
Optional<Object> o = Optional.of(null);
System.out.println(o);
}
static Optional ofNullable(T value) : 得到一个包含元素的容器对象,里面一定有元素,元素可以是非null。可以是null
代码演示如下:
@Test
public void test02(){
String str=null;
Optional<String> str1 = Optional.ofNullable(str);//允许为null
System.out.println(str1);
}
12.3.2 取出容器中的数据
T get(): 要求容器中必须有一个非空对象
T orELse(T other) : 如果容器中有非空对象,就取容器中非空对象,如果没有,就用other备胎。
代码演示如下:
@Test
public void test03(){
Optional<Integer> maxOptional = Stream.of(1, 3, 5, 7, 9)
.filter(t -> t % 2 == 0)
.max(Integer::compareTo);
// Integer integer = maxOptional.get();//java.util.NoSuchElementException: No value present maxOptional里值为空
Integer max = maxOptional.orElse(0);
System.out.println(max);
}
T orELseget(Supplier<? extends T>other): 如果容器中有非空对象,就取容器中非空对象,如果没有,就用Supplier接口提供的对象代替
代码演示如下:
@Test
public void test04(){
Random random=new Random();
Optional<Integer> maxOptional = Stream.of(1, 3, 5, 7, 9)
.filter(t -> t % 2 == 0)
.max(Integer::compareTo);
// Integer integer = maxOptional.get();//java.util.NoSuchElementException: No value present maxOptional里值为空
Integer max = maxOptional.orElseGet(() -> random.nextInt(100)); //若无偶数,则返回一个100以内的随机整数
System.out.println(max);
}
T orElseThrow(Supplier<? extends X> exceptionSupplier) :
如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException
代码演示如下:
@Test
public void test05(){
Random random=new Random();
Optional<Integer> maxOptional = Stream.of(1, 3, 5, 7, 9)
.filter(t -> t % 2 == 0)
.max(Integer::compareTo);
// Integer integer = maxOptional.get();//java.util.NoSuchElementException: No value present maxOptional里值为空
Integer max = maxOptional.orElseThrow(() -> new RuntimeException("没有最大值")); //没有偶数,就抛出里面的异常
System.out.println(max);
}
12.3.3 其他
boolean isPresent() : 判断容器中有没有元素
void ifPresent(Consumer<? super T> consumer) : 如果存在就执行xx代码
代码演示如下:
@Test
public void test06(){
Optional<Integer> maxOptional = Stream.of(1, 3, 5, 7, 9)
.filter(t -> t % 2 == 0)
.max(Integer::compareTo);
// Integer integer = maxOptional.get();//java.util.NoSuchElementException: No value present maxOptional里值为空
maxOptional.ifPresent(s -> System.out.println(s)); //若有最大的偶数,就打印,没有就什么都不打印
}