第三十一章
一、Stream流
1.1 使用传统的方式遍历集合,对集合中的数据进行过滤
/*
使用传统的方式,遍历集合,对集合数据进行遍历
*/
public class Demo01List {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三丰");
list.add("张三");
//遍历集合,存储张开头的元素到集合中
List<String> list1 = new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")){
list1.add(s);
}
}
//遍历集合,存储名字长度为3的
List<String> list2 = new ArrayList<>();
for (String s : list1) {
if (s.length() >= 3){
list2.add(s);
}
}
//循环打印
for (String s : list2) {
System.out.println(s);//张无忌 张三丰
}
}
}
1.2 使用传统的方式遍历集合,对集合中的数据进行过滤
/*
使用Stream流方式遍历,过滤
Stream流是JDK1.8之后出现的
*/
public class Demo01Stream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三丰");
list.add("张三");
//遍历集合,存储张开头的元素到集合中
//遍历集合,存储名字长度为3的
//循环打印
list.stream()
.filter(name->name.startsWith("张"))
.filter(name->name.length()==3)
.forEach(name-> System.out.println(name));
/*
打印结果
张无忌
张三丰
*/
}
}
1.3 流式思想概述
1.4 两种获取Stream流的方式
/*
java.util.stream.Stream<T> 是java8新加入的最常用的流接口。
获取一个流非常简单,有以下几种常用的方式:
所有的Collection集合都可以通过stream默认方法获取流。
default Stream<E> stream()
Stream 接口的静态方法of可以获取数组对应的流
static <T> Stream<T> of (T...values)
参数是一个可变参数,那么我们就可以传递一个数组
*/
public class Demo02GetStream {
public static void main(String[] args) {
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,存储到一个set集合中
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键值与映射的关系 entrySet)
Set<Map.Entry<String,String>> entries = map.entrySet();
Stream<Map.Entry<String,String>> stream5 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1,2,3,4,5);
//可变参数可以传递数组
Integer[] arr ={1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
String[] arr2 ={"a","bb","ccc"};
Stream<String> stream8 = Stream.of(arr2);
}
}
1.8 Stream流中的常用方法—forEach(终结方法)
/*
Stream流中的常用方法——forEach
void forEach(Consumer<? super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数处理。
Consumer是一个消费型的函数式接口,可以传递Lambda表达式,消费数据
简单记:"
forEach方法,用来遍历流中的数据
是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
*/
public class Demo03SteamForEach {
public static void main(String[] args) {
//获取一个Stream流
Stream<String> stream = Stream.of("张三","李四","王五","赵六");
//使用Stream流中的方法forEach对Stream流中的数据进行遍历
/* stream.forEach((String name)->{
System.out.println(name);
});*/
stream.forEach( name-> System.out.println(name));
}
}
1.9 Stream流中的常用方法—filter
/*
Stream流中的常用方法filter:用于对Stream流中的数据进行过滤
Stream<T> filter(Predicate<? super ?> predicate);
filter方法的参数是一个函数式接口,可传递Lambda表达式
Predicate中的抽象方法:boolean test(T t)
*/
public class Demo04Streamfilter {
public static void main(String[] args) {
//创建一个Stream流
Stream<String> stream = Stream.of("张三丰","张翠山","赵敏","周芷若");
//对Stream流中的元素进行过滤,只要姓张的人
Stream<String> stream2 =stream.filter((String name)->{
return name.startsWith("张");
});
//遍历Stream2流
stream2.forEach(name-> System.out.println(name));
/*
Stream 流属于管道流,只能被消费一次
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
*/
//遍历Stream流
stream.forEach(name-> System.out.println(name));
//IllegalStateException: stream has already been operated upon or closed
}
}
1.10 Stream流中的特点-只能使用一次
Stream 流属于管道流,只能被消费一次
/*
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
*/
//遍历Stream流
stream.forEach(name-> System.out.println(name));
//IllegalStateException: stream has already been operated upon or closed
1.11 Stream流中的常用方法—map(映射)
如果需要将流中的元素映射到另一个流中,可以使用map方法:
<R> Stream<R> map(Function<? super T,? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
Function中的抽象方法:
R apply(T t);
public class Demo05Stream_map {
public static void main(String[] args) {
//获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "3", "4");
//使用map方法,把字符串类型的整数转换(映射)为Integer类型的整数
Stream<Integer> stream2 = stream.map((String s)->{
return Integer.parseInt(s);
});
//遍历Stream2流
stream2.forEach(i-> System.out.println(i));
}
}
1.11 Stream流中的常用方法—count(终结方法)
import java.util.ArrayList;
import java.util.stream.Stream;
/*
Stream流中的方法——count:用于统计Stream流中的元素个数
long count();
count方法是一个中介方法,返回值是一个long类型的整数
所以不能再继续调用Stream流中的其他方法了
*/
public class Demo06Count {
public static void main(String[] args) {
//获取一个Stram流
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
Stream<Integer> stream = list.stream();
long count = stream.count();
System.out.println(count);//6
}
}
1.12 Stream流中的常用方法—limit
/*
Stream流中的方法——limit:用于截取流中的元素
limit方法可以对流进行截取,只取用前n个.方法签名:
Stream<T> limit(long maxSize);
参数是一个long 类型,如果集合长度大于参数则进行截取,否则不进行操作
limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
*/
public class Demo07Limit {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
//使用limit对Stream流中的元素进行截取,只要前三个元素
Stream<String> stream2 = stream.limit(3);
//遍历stream2流
stream2.forEach(str-> System.out.println(str));//美羊羊喜羊羊懒羊羊
}
}
1.13 Stream流中的常用方法—skip
/*
Stream流中的方法——skip:用于跳过元素
如果流的当前长度大于n,则跳过前n个否则就会得到一个长度为0的空流
*/
import java.util.stream.Stream;
public class Demo08Skip {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
//使用skip跳过Stream流中的前三个元素
Stream<String> stream2 = stream.skip(3);
//遍历stream2流
stream2.forEach(str-> System.out.println(str));//灰太狼 红太狼
}
}
1.14 Stream流中的常用方法—concat(组合方法)
/*
Stream.concat( stream1 ,stream2) 将两个流合并到一起
*/
public class Demo09Concat {
public static void main(String[] args) {
//创建两个Stream流
Stream<String> stream1 = Stream.of("张三丰","张翠山","赵敏","周芷若");
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream2 = Stream.of(arr);
//把以上两个Stream流合并到一起
Stream<String> concat = Stream.concat(stream1,stream2);
//遍历concat流
concat.forEach((name-> System.out.println(name)));
}
}
1.15 练习:集合元素处理(传统方式)
/*
题目:
现在有两个Arraylist集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)
依次进行以下若干操作步骤:
1.第一个队伍只要名字为3个字的成员姓名;存储到一一个新集合中。
2.第一个队伍筛选之后只要前3个人;存储到-一个新集合中。
3.第二个队伍只要姓张的成员姓名;存储到一一个新集合中。
4.第二个队伍筛选之后不要前2个人;存储到一一个新集合中。
5.将两个队伍合并为一一个队伍;存储到一一个新集合中。
6.根据姓名创建Person对象;存储到一个新集合中。
7.打印整个队伍的Person对象信息。
*/
public class Demo01Test {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石终与");
one.add("老子");
one.add("庄子");
one.add("洪七公");
// 1.第一个队伍只要名字为3个字的成员姓名;存储到一一个新集合中。
ArrayList<String> one1 = new ArrayList<>();
for (String name : one) {
if (name.length()==3){
one1.add(name);
}
}
// 2.第一个队伍筛选之后只要前3个人;存储到-一个新集合中。
ArrayList<String> one2 = new ArrayList<>();
for (int i = 0; i < 3; i++) {
one2.add(one1.get(i)); //i=0 ,1,2
}
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
// 3.第二个队伍只要姓张的成员姓名;存储到一一个新集合中。
ArrayList<String> two1 = new ArrayList<>();
for (String name : two) {
if (name.startsWith("张")){
two1.add(name);
}
}
//4.第二个队伍筛选之后不要前2个人;存储到一一个新集合中。
ArrayList<String> two2 = new ArrayList<>();
for (int i =2;i<two1.size();i++){
two2.add(two1.get(i));//i不包含0 1
}
//5.将两个队伍合并为一一个队伍;存储到一一个新集合中。
ArrayList<String> all = new ArrayList<>();
//addAll添加一个集合
all.addAll(one2);
all.addAll(two2);
// 6.根据姓名创建Person对象;存储到一个新集合中。
ArrayList<Person> list = new ArrayList<>();
for (String name : all) {
list.add(new Person(name));
}
// 7.打印整个队伍的Person对象信息。
for (Person person : list) {
System.out.println(person);
}
}
}
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1.16 练习:集合元素处理(Stream流方式)
/*
1.第一个队伍只要名字为3个字的成员姓名;存储到一一个新集合中。
2.第一个队伍筛选之后只要前3个人;存储到-一个新集合中。
3.第二个队伍只要姓张的成员姓名;存储到一一个新集合中。
4.第二个队伍筛选之后不要前2个人;存储到一一个新集合中。
5.将两个队伍合并为一一个队伍;存储到一一个新集合中。
6.根据姓名创建Person对象;存储到一个新集合中。
7.打印整个队伍的Person对象信息。
*/
public class Demo02Stream {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石终与");
one.add("老子");
one.add("庄子");
one.add("洪七公");
// 1.第一个队伍只要名字为3个字的成员姓名;存储到一一个新集合中。
//2.第一个队伍筛选之后只要前3个人;存储到-一个新集合中。
Stream<String> onestream = one.stream().filter(name->name.length()==3).limit(3);
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
// 3.第二个队伍只要姓张的成员姓名;存储到一一个新集合中。
// 4.第二个队伍筛选之后不要前2个人;存储到一一个新集合中。
Stream <String> twoStream= two.stream().filter(name->name.startsWith("张")).skip(2);
// 5.将两个队伍合并为一一个队伍;存储到一一个新集合中。
// 6.根据姓名创建Person对象;存储到一个新集合中。
// 7.打印整个队伍的Person对象信息。
Stream.concat(onestream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));
}
}
二、方法引用
2.1 方法引用基本介绍
public class Demo01Printable {
//定义一个方法,参数传递printable接口,对字符串进行打印
public static void printString(Printable p){
p.print("HelloWorld");
}
public static void main(String[] args) {
//调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda
printString((s)->{
System.out.println(s);
});
/*
Lambda表达式的目的,打印参数传递的字符串
把参数s,传递给了System. out対象,凋用out对象中的方法println对字符串迸行了输出
注意:
1.System. out对象已经存在的
2.println方法也是已经存在的
所以我们可以使用方法引用来代化Lambda表达式
可以使用System. out方法直接引用println方法
*/
printString(System.out::println);
}
}
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
//打印字符串的方法
void print(String s);
}
2.2 方法引用-通过对象名引用成员方法
主方法
/*
通过对象名引用成员方法
使用前提是对象名是已经存在的,成员方法也是已经存在的
就可以使用对象名来引用成员方法
*/
public class Demo02ObjectMethodReference {
//定义一个方法,方法的参数传递Printable接口
public static void printString(Printable p){
p.print("hello");
}
public static void main(String[] args) {
//调用printString方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
printString((s)->{
//创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
//调用 MethodRerObject对象中的成员方法printUpperCaseString,大写输出字符串
obj.printUpperCaseString(s);
});
/*
使用方法引用优化
对象已经存在了MethodRerObject
成员方法也是已经存在的printUpperCaseString
所以我们可以使用对象名引用成员方法
*/
MethodRerObject obj = new MethodRerObject();
printString(obj::printUpperCaseString);
}
}
函数式接口
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
//打印字符串的方法
void print(String s);
}
生产对象的类
public class MethodRerObject {
//定义一个成员方法,传递字符串,把字符串按照大写输出
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
2.3 方法引用-通过类名引用静态成员方法
/*
通过类名引用静态成员方法
类已经存在,静态成员方法也存在
*/
public class Static {
//定义一个方法,方法的参数传递要计算整数的绝对值,和函数式接口Calcable
public static int method(int number,Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args) {
int number= method(-10,(n)->{
//绝对值计算
return Math.abs(n);
});
System.out.println(number);//10
/*
方法引用优化
Math类存在 abs 静态方法存在
*/
int number2= method(-10,Math::abs);
System.out.println(number2);//10
}
}
2.4 方法引用-通过类名引用静态成员方法
/*
通过类名引用静态成员方法
类已经存在,静态成员方法也存在
*/
public class Static {
//定义一个方法,方法的参数传递要计算整数的绝对值,和函数式接口Calcable
public static int method(int number,Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args) {
int number= method(-10,(n)->{
//绝对值计算
return Math.abs(n);
});
System.out.println(number);//10
/*
方法引用优化
Math类存在 abs 静态方法存在
*/
int number2= method(-10,Math::abs);
System.out.println(number2);//10
}
}
@FunctionalInterface
public interface Calcable {
//定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
int calsAbs(int number);
}
2.5 方法引用-通过super引用父类的成员方法
/*
定义父类
*/
public class Human {
public void sayHello(){
System.out.println("Hello,我是Human");
}
}
/*
定义见面的函数式接口
*/
@FunctionalInterface
public interface Greetable {
//定义一个见面的方法
void greet();
}
/*
定义子类
*/
public class Man extends Human{
@Override
public void sayHello() {
System.out.println("我是Man");
}
//定义一个方法,参传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,方法的参数Greetable是一个函数式接口,所以可以传递Lambda
/* method(()->{
//创建父类Human对象
Human h = new Human();
//调用父类对象
h.sayHello();
});*/
//因为有子父类关系,所以存在一个关键字super代表父类,
// 所以我们可以直接使用super调用父类的成员方法
/* method(()->{
super.sayHello();
});*/
/*
使用super引用父类的成员方法super是已经存在的
父类的成员方法sayHello也是已经存在的
所以我们可以直接用super引用父类的成员方法
*/
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
2.5 方法引用-通过this引用本类的成员方法
/*
通过this引用本类的成员方法
*/
public class Husband {
//定义一个买房子的方法
public void buyHouse(){
System.out.println("北京买房");
}
//定义一个结婚的方法
public void marry(Richable r){
r.buy();
}
//定义一个非常高兴的方法
public void soHappy(){
//调用结婚的方法,方法的参数Richable是一个函数式接口,传递Lambda表达式
/* marry(()->{
//使用this.成员方法调用本类买房子的方法
this.buyHouse();
});*/
/* 使用方法引用优化Lambda表达式
this是已经存在的,本类成员的方法buyHouse也是存在的
所以我们可以直接使用this引用本类的成员方法buyHouse
*/
marry(this::buyHouse);
}
public static void main(String[] args){
new Husband().soHappy();
}
}
@FunctionalInterface
public interface Richable {
//定义一个方法
void buy();
}
2.6 方法引用—类的构造器(构造方法)引用
/*
类的构造器(构造方法)引用
*/
public class Demo {
//定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name ,PersonBuilder pb){
Person person = pb.builderPerson(name);
System.out.println(person.getName());
}
public static void main(String[] args) {
//调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda
printName("迪丽热巴",(String name)->{
return new Person(name);
});
/*
使用方法引用优化Lambda
构造方法new Person(String name)已知
创建对象已知
就可以使用Person引用new 创建对象
*/
printName("古力娜扎",Person::new);//使用Person类的带参构造方法,传递姓名创建对象
}
}
/*
定义一个创建Person对象的函数式接口
*/
@FunctionalInterface
public interface PersonBuilder {
//定义一个方法,根据传递的姓名,创建Person对象返回
Person builderPerson(String name);
}
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.6 方法引用—数组构造器引用
/*
数组的构造器引用
*/
public class Demo {
/*定义一个方法
方法的参数传递创建数组的长度和ArrayBuilder接口
方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
*/
public static int[] createArray(int length,ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args) {
//调用create方法,传递数组的长度和Lambda表达式
int arr1[] = createArray(10,(len)->{
//根据数组的长度创建并返回
return new int[len];
});
System.out.println(arr1.length);
/*
使用方法引用优化Lambda
已知创建的就是int[]类型的数组
数组长度已知
就可以使用方法引用
int[]引用new,根据参数传递的长度来创建数组
*/
int[] array2 = createArray(10,int[]::new);
System.out.println(Arrays.toString(array2));
System.out.println(array2.length);//10
}
}
/*
定义一个创建数组的函数式接口
*/
@FunctionalInterface
public interface ArrayBuilder {
//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}