Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
循环遍历的弊端 Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How)
for循环的语法就是“怎么做”
for循环的循环体才是“做什么”
遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
Stream流的更优写法:
import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream()
.filter(s ‐> s.startsWith("张"))
.filter(s ‐> s.length() == 3)
.forEach(System.out::println);
}
}
1.1 流式思想概述
filter 、map 、skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
Stream(流)是一个来自数据源的元素队列
1、元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
2、数据源 流的来源。 可以是集合,数组 等。
3、和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluentstyle)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短(shortcircuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
1.2 获取流java.util.Stream是java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
-所有的Collection集合都可以通过stream默认方法获取流;
default Streamstream ()
-Stream接口的静态方法of可以获取数组对应的流。
staticStream of (T...values)
参数是一个可变参数,那么我们就可以传递一个数组
java.util.Map 接口不是Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
public static void main(String[] args) {
Map map = new HashMap<>();
// ...
Stream keyStream = map.keySet().stream();
Stream valueStream = map.values().stream();
Stream> entryStream = map.entrySet().stream();
}
}
根据数组获取流 如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream 接口中提供了静态方法of ,使用很简单
import java.util.stream.Stream;
public class Demo06GetStream {
public static void main(String[] args) {
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream stream = Stream.of(array);
}
}
Stream流属于管道流,只能别消费(使用)一次
第一个Stream流调用完毕方法,数据就会转到下一个Stream上,儿这个时候第一个Stream流已经使用完毕,关闭了。第一个流就不能再调用方法了。
1.3 Stream流常用方法 1.3.1 map将一种数据类型转换为另一种类型,就叫映射。 R apply( T t)
import java.util.stream.Stream;
public class Demo08StreamMap {
public static void main(String[] args) {
//创建一个Stream流
Stream stream = Stream.of("1", "2", "3");
Stream stream2 = stream.map(str‐>Integer.parseInt(str));
}
}个数
1.3.2 统计:count
正如旧集合Collection 当中的size 方法一样,流提供count 方法来数一数其中的元素个数:long count();
这事一个终结方法,返回值是一个Lang类型的整数,不能再继续调用Stream中的其他方法了。
1.3.3 limt方法用于截取流中的元素。limit方法可以对流进行截取,只取用前n个,方法签名:Stream
import java.util.stream.Stream;
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream original = Stream.of("张无忌", "张三丰", "周芷若");
Stream result = original.limit(2);
System.out.println(result.count()); // 2
}
}
1.3.4 skip
如果希望跳过前几个元素,可以使用skip 方法获取一个截取之后的新流:Stream
import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
//获取一个Strem流
Stream original = Stream.of("喜羊羊", "懒羊羊", "美羊羊","灰太狼", "红太狼");
//使用skip方法跳过前3个元素
Stream result = original.skip(2);
System.out.println(result.count()); // 1
}
}
1.3.5 组合:concat
如果有两个流,希望合并成为一个流,那么可以使用Stream 接口的静态方法concat :static
备注:这是一个静态方法,与java.lang.String 当中的concat 方法是不同的。
import java.util.stream.Stream;
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream streamA = Stream.of("张无忌");
Stream streamB = Stream.of("张翠山");
Stream result = Stream.concat(streamA, streamB);
}
}
1.4 练习
public class DemoStream {
public static void main(String[] args) {
//第一支队伍
ArrayList one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋院桥");
one.add("苏炳添");
one.add("王中王");
one.add("刘德华");
one.add("老子");
one.add("庄子");
one.add("郭富城");
//,第一个队伍只要名字为三个字的呈圆形姓名,存储到一个新的集合中
ArrayList one1 = new ArrayList<>();
for (String s:one)
{
if (s.length()==3){
one1.add(s);
}
}
ArrayList one2 = new ArrayList<>();
for (int i = 0; i < 3; i++) {
one2.add(one1.get(i));
}
//第二支队伍
ArrayList two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯");
two.add("张天爱");
two.add("张够大");
//第二支队伍只要姓张的成员姓名,存储到新的集合中
ArrayList two1 = new ArrayList<>();
for (String s:two) {
if(s.startsWith("张")) {
two1.add(s);
}
}
//4,第二支队伍筛选之后不要前两个人,存储到一个新的集合中。
ArrayList two2 = new ArrayList<>();
for (int i = 2; i all = new ArrayList<>();
all.addAll(one2);
all.addAll(two2);
//6根据姓名创建Person对象,存储到一个新的集合中。
ArrayList list = new ArrayList<>();
for (String name:all)
{
list.add(new Person(name));
}
//打印每每一个Person
for (Person p:list) {
System.out.println(p);
}
}
}
用Stream流实现,优化
public class Demo01Stream {
public static void main(String[] args) {
//第一支队伍
ArrayList one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋院桥");
one.add("苏炳添");
one.add("王中王");
one.add("刘德华");
one.add("老子");
one.add("庄子");
one.add("郭富城");
//1 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
//2,第一个队伍筛选之后只要前3个人;存储到一个新集合中
Stream oneS = one.stream().filter(name->name.length()==3).limit(3);
//第二支队伍
ArrayList two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯");
two.add("张天爱");
two.add("张够大");
//第二支队伍只要姓张的成员姓名,存储到一个新的集合中
//不要前两个人,存储到一个新的集合中
Stream twoS = two.stream().filter(name->name.startsWith("张")).skip(2);
//两只队伍合并,存储
//根据姓名创建Person对象;存储到一个新的集合中
//打印Person对象
Stream.concat(oneS,twoS).map(name->new Person(name)).forEach(s-> System.out.println(s));
};
}
二 方法引用
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。
来看一个简单的函数式接口以应用Lambda表达式:
@FunctionalInterface
public interface Printable {
void print(String str);
}
在Printable 接口当中唯一的抽象方法print 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单:
public class Demo01PrintSimple {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(s ‐> System.out.println(s));
}
}
其中printString 方法只管调用Printable 接口的print 方法,而并不管print 方法的具体实现逻辑会将字符串打印到什么地方去。而main 方法通过Lambda表达式指定了函数式接口Printable 的具体操作方案为:拿到String(类型可推导,所以可省略)数据后,在控制台中输出它。
分析: Lambda表达式的目的,打印参数传递的字符串 把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
注意:
1,System.out对象是已经存在的 2,println方法也是已经存在的
所以我们可以使用方法引用来优化Lambda表达式 可以使用System.out直接引用(调用)println方法
public class Demo02PrintRef {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(System.out::println);
}
方法引用符
双冒号:: 为引用运算符,而它所在的表达式被称为方法引用。如Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
例如上例中, System.out 对象中有一个重载的println(String) 方法恰好就是我们所需要的。那么对于printString 方法的函数式接口参数,对比下面两种写法,完全等效:
Lambda表达式写法: s -> System.out.println(s);
方法引用写法: System.out::println
第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println 方法去处理。
第二种等效写法的语义是指:直接让System.out 中的println 方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案
注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常
2.1 通过对象名引用成员方法public class Demo04MethodRef {
//成员方法存在
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
//创建对象
MethodRefObject obj = new MethodRefObject();
//方法引用
printString(obj::printUpperCase);
}
}
2.2 通过类名引用静态方法
通过类名引用静态成员方法。
类已经存在,静态方法也已经存在,就可以使用方法引用
例子,去绝对值
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
2.3 通过super引用成员方法
在这个例子中,下面两种写法是等效的:
Lambda表达式: () -> super.sayHello()
方法引用: super::sayHello
2.4 通过this引用本类方法在这个例子中,下面两种写法是等效的: Lambda表达式: () -> this.buyHouse() 方法引用: this::buyHouse
2.5 类的构造器引用由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new 的格式表示
Lambda表达式: name -> new Person(name) 方法引用: Person::new
2.6 数组的构造器引用数组也是Object 的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口。
等效的写法:
Lambda表达式: length -> new int[length]
方法引用: int[]::new



