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) { List1.1 流式思想概述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); } }
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) { Mapmap = 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 = { "张无忌", "张翠山", "张三丰", "张一元" }; Streamstream = 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流 Stream1.3.2 统计:countstream = Stream.of("1", "2", "3"); Stream stream2 = stream.map(str‐>Integer.parseInt(str)); } }个数
正如旧集合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) { Stream1.3.4 skiporiginal = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.limit(2); System.out.println(result.count()); // 2 } }
如果希望跳过前几个元素,可以使用skip 方法获取一个截取之后的新流:Stream
import java.util.stream.Stream; public class Demo11StreamSkip { public static void main(String[] args) { //获取一个Strem流 Stream1.3.5 组合:concatoriginal = Stream.of("喜羊羊", "懒羊羊", "美羊羊","灰太狼", "红太狼"); //使用skip方法跳过前3个元素 Stream result = original.skip(2); System.out.println(result.count()); // 1 } }
如果有两个流,希望合并成为一个流,那么可以使用Stream 接口的静态方法concat :static
备注:这是一个静态方法,与java.lang.String 当中的concat 方法是不同的。
import java.util.stream.Stream; public class Demo12StreamConcat { public static void main(String[] args) { Stream1.4 练习streamA = Stream.of("张无忌"); Stream streamB = Stream.of("张翠山"); Stream result = Stream.concat(streamA, streamB); } }
public class DemoStream { public static void main(String[] args) { //第一支队伍 ArrayListone = 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
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)