目录
Stream流
用于解决已有集合类库既有的弊端
例如:List的循环遍历
循环是遍历的一种方式,而目的是遍历
循环遍历和Stream流方式遍历的对比
public class Demo9 {
//for循环方式遍历,共使用了3个循环
@Test
public void test(){
List<String> list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> listA=new ArrayList<>();
for (String s : list)
if (s.startsWith("张"))
listA.add(s);
List<String> listB=new ArrayList<>();
for (String s : listA)
if (s.length()==3)listB.add(s);
for (String s : listB) {
System.out.println(s);
}
}
//Stream的更优写法,1.8之后出现
@Test
public void test2(){
List<String> list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream().filter(name->name.startsWith("张"))
.filter(name->name.length()==3)
.forEach(System.out::println);
}
/*
结果一样:
流式思想:
拼接流式模型:建立一个生产线,按照生产线来生产商品
上面的代码可以理解为:生产线上只要性张的且名字3个字的商品
*/
}
类似于这张图,展示了过滤、映射、跳过等多步操作,这是一种集合元素的处理方案,而方案是一种函数模型,图中每一个方框都是一个流,调用指定的方法,可以从一个流模型转换为另一个流模型,右侧的3是最终结果
得益于Lambda表达式的延迟执行,只有当终结方法count执行时,整个模型才按照指定策略执行操作,也就是不会造成资源的浪费
元素是特点类型的对象,形成一个队列,Stream不会存储元素,是按需计算
数据源:可以是集合,数组等
Stream操作还有两个基本特征
Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样可以对操作进行优化,比如延迟执行和短路
内部迭代:以前对集合的遍历都是迭代器或增强for,在集合外部进行迭代,Stream提供了内部迭代的方式,流可以直接调用遍历方法
使用一个流的时候通常包含三个步骤:获取一个数据源source->数据转换->执行操作获取想要的结果,每次转换原有的Stream对象不变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道
两种获取流的方式
java.util.stream.Stream<T>是java8新加入的常用流接口(非函数式接口)
获取流的方式:
1、所有的Collection集合都可以通过stream默认方法获取流
default Stream<E> stream()
2、Stream接口的静态方法of可以获取数组对应的流
static <T> Stream<T> of (T… values)
参数是一个可变参数可以传递一个数组
@Test
public void test(){
//把集合转换为Stream流
//先转List集合
List<String> list=new ArrayList<>();
Stream<String> stream1=list.stream();
//转Set集合
Set<String> set=new HashSet<>();
Stream<String> stream2 = set.stream();
//转Map集合
Map<String,String> map=new HashMap<>();
//间接将Map集合转化成Stream流
//获取键
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
//获取值
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对
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","b","c"};
Stream<String> stream8 = Stream.of(arr2);
}
public class Demo10 {
//这个管道流Stream可以理解为将数据向流水线一样一个一个的往前送
/*
常用方法
分为:
延迟方法:返回值任然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法其余方法都为延迟方法)
终结方法:返回值不再是Stream接口自身类型的方法,因此不再支持类似stringBuilder那样的链式调用
终结方法包括count、forEach等
逐一处理:forEach(终结方法)
void forEach(Consumer<? super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
*/
//forEach演示
@Test
public void test2(){
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
//被代替的写法(String name)->System.out.println(name)
stream.forEach(System.out::println);
}
/*
过滤:filter,可以通过filter方法将一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
接收一个Predicate函数式接口作为筛选条件
*/
//filter演示
@Test
public void test3(){
Stream<String> stream1 = Stream.of("张三丰", "张翠山", "张三", "赵敏", "周芷若", "张无忌");
//过滤掉不姓张的人
Stream<String> stream2 = stream1.filter((name) -> name.startsWith("张"));
stream2.forEach(System.out::println);
/*
Stream流属于管道流,只能消费(使用一次)
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
*/
//尝试用第一个Stream流再次调用方法
stream1.forEach(System.out::println);
}
/*
映射:map
如果需要将流中的元素映射到另一个流中,可以使用map方法
<R> Stream<R> map(Function<? super T,? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的流
*/
//map演示
@Test
public void test4(){
Stream<String> stream = Stream.of("1", "2", "3", "4");
//被代替的写法(s)->{ return Integer.parseInt(s); }
Stream<Integer> stream2 = stream.map(Integer::parseInt);
stream2.forEach(System.out::print);
}
/*
统计个数:count(终结方法)
正如旧集合当中的size方法一样,流提供count方法来数一数其中元素的个数
long count();
*/
//count演示
@Test
public void test5(){
ArrayList<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
Stream<Integer> stream = list.stream();
long count = stream.count();
System.out.println(count);
}
/*
limit方法可以对流进行截取,只取用前n个
Stream<T> limit(long maxSize);
如果集合当前长度大于参数长度则进行截取,否则不进行截取,否则不进行操作
*/
//limit演示
@Test
public void test6(){
String [] arr={""};
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
stream.limit(3).forEach(System.out::println);
}
/*
跳过前几个:skip
如果希望跳过前几个元素,可以使用skip方法获取一个截取后的新流
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n;否则将会得到一个长度为0的空流
*/
//skip演示
@Test
public void test7(){
String [] arr={""};
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
stream.skip(3).forEach(System.out::println);
}
/*
组合:concat
如果有两个流,希望合并为一个流,那么可以使用Stream接口的静态方法concat
static <T> Stream concat(String<? extends T> a,Stream<? extends T> b)
备注:这是一个静态方法,与java.lang.String当中的concat方法不同
*/
//concat演示
@Test
public void test8(){
Stream<String> stream1 = Stream.of("1", "2", "3", "4", "5");
Stream<String> stream2 = Stream.of("张三丰", "张翠山", "张三", "赵敏", "周芷若", "张无忌");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(System.out::println);
}
//练习
@Test
public void test9(){
ArrayList<String> one=new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
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("张二狗");
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);
//被代替的写法Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p->System.out.println(p));
Stream.concat(oneStream, twoStream).map(Person::new).forEach(System.out::println);
}
}
方法引用
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作
考虑一种情况:若我们在Lambda中所指定的操作方案,已经有地方存在相同的方案,那么还有必要重复写吗?
public class Demo1 {
public static void printString(Printable p){
p.print("HelloWorld");
}
@Test
public void test(){
printString((s)->System.out.println(s));
/*
分析:该Lambda表达式的目的,打印参数传递的字符串
把s传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
注意:
1、System.out对象是已经存在的
2、println方法也是已经存在的
所以我们可以使用方法引用来优化Lambda
*/
printString(System.out::println);
//::是方法引用的运算符,如果Lambda要表达的函数方案已经存在于某个方法的实现中
//那么通过::来引用该方法作为Lambda的替代者
//注意:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
}
}
两次打印结果一致
上面例子中使用的函数式接口:
/*
打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
void print(String s);
}
通过各种方式引用成员方法
/*引用对象方法案例*/
public class MethodRerObj {
//定义一个成员方法,传递字符串,把字符串按照大写输出
public void printUpperCase(String str){
System.out.println(str.toUpperCase());
}
//不想再写一个接口了,直接使用Consumer接口
public static void printString(Consumer<String> con){
String s="hello";
con.accept(s);
}
public static void main(String[] args) {
MethodRerObj obj=new MethodRerObj();
printString(obj::printUpperCase);
}
//通过类名引用静态方法
//该方法的参数传递要计算绝对值的整数,利用Function接口实现
public static int method(int number, Function<Integer,Integer> fun){
return fun.apply(number);
}
@Test
public void test1(){
int number2= method(-10,Math::abs);
System.out.println(number2);
}
//此例子有点抽象!,其中写了一个函数式接口,一个父类包含一个见面方法,一个子类继承自父类
//其中子类重写了父类的见面方法,子类中还包含一个show方法测试super关键字的引用
//在类的外部使用@Test注解,创建子类对象进行运行,结果表名super::sayHello调用的是父类的方法
//通过super引用成员方法
//当存在继承关系时,当Lambda中需要出现super调用时,也可以使用方法引用进行代替
//定义一个父类
class Fu{
//该构造方法用于测试构造器引用
public Fu(){
System.out.println("我是构造方法");
}
//用于测试构造器引用
public void showFuConstruct(GreetInterface greetInterface){
greetInterface.greet();
}
public void sayHello(){
System.out.println("hello!,我是父类");
}
}
//定义一个函数式接口
@FunctionalInterface
public interface GreetInterface {
//定义一个见面方法
void greet();
}
//定义子类
class Zi extends Fu{
//重写sayHello
@Override
public void sayHello(){
System.out.println("hello!,我是子");
}
public void method2(GreetInterface greetInterface){
greetInterface.greet();
}
//用于测试super引用
public void show(){
method2(super::sayHello);
}
//该方法用于测试this引用
public void show2(){
method2(this::sayHello);
}
}
@Test
public void test2(){
Zi zi =new Zi();
zi.show();
}
//通过this引用本类的成员方法
@Test
public void test3(){
Zi zi =new Zi();
zi.show2();
}
//类的构造器引用(构造方法)
//使用方法:类名::new
@Test
public void test4(){
new Fu().showFuConstruct(Fu::new);
}
/*
数组的构造器引用
*/
//定义一个方法参数传递创建数组的长度和Function接口
public static int[] creatArray(int length,Function<Integer,int[]> fun){
return fun.apply(length);
}
@Test
public void test5(){
int[] arr1=creatArray(10,(len)->{
return new int[len];
});
/*使用方法引用优化Lambda表达式
一直创建的就是int[] 数组
数组的长度也是已知的
就可以使用方法引用
int[]::new,根据参数的长度来创建数组
*/
creatArray(10, int[]::new);
System.out.println(arr1.length);
}
}