1.Stream流

1.for循环带来的弊端

  • 在jdk8中,lambda专注于做什么,而不是怎么做
  • for循环的语法就是怎么做
  • for循环的循环体才是做什么

遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

集合存储案列:

import java.util.ArrayList;
import java.util.List; public class Demo{
public static void main(String[] args){
// 创建一个list集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("赵强");
list.add("张三丰"); //对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
list<String> listA = new ArrayList<>();
for(String str:list){
if(str.startsWidth("张")){
listA.add(str);
}
}
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
List<String> ListB = new ArrayList<>();
for(String s:listA){
if(s.length()==3){
listB.add(s);
}
}
}
}

2.使用Stream的更优写法

    import java.util.ArrayList;
import java.util.List; public class Demo{
public static void main(String[] args){
// 创建一个list集合,存储姓名
List<String> 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);
}
}

流思想

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,二十按需计算。
  • 数据源流的来源,可以是 集合, 数组等。

和以前的Collection操作不同,Stream操作还有两个基础的特征:

  • Pipelining:中间操作都会返回流对象本省。着多个操作可以串联一个管道,如同流式风格,可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。

  • 内部迭代:以前对集合变量都是通过Iterator或者是增强for循环的方式,显示在集合外部进行迭代,这种就是外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。

  • 使用流的步骤

    获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。

3.获取流

获取一个流的方法非常简单,有以下几种常用的方式:

  • 所有的Collection集合都可以通过Stream默认方法获取流;

    • default Stream<E> stream()
  • Stream接口的静态方法of可以获取数据对应的流。
    • static <T> Stream<T> of(T.. values)
    • 参数是一个可变参数,那么可以传递一个数组
    public class Demo{
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,6);
//可变参数传递数组
Integer[] arr = {1,2,3,4,5,6};
Stream<Integer> stream7 = Stream.of(arr);
Strng[] arr2 = {"a", "bn", "cd"};
Stream<String> stream8 = Stream.of(arr2);
}
}

4.常用方法

forEach方法

Stream流常用方法 forEach

  • void forEach(Consumer<? super T> action):
  • 该方法节后一个Consumer接口函数,会将每一个流元素交给该函数进行处理。
  • Consumer接口是一个消费型的函数式接口, 可以传递Lambda表达式,消费数据

简单记:

  • forEach方法,用来遍历流中的数据
  • 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
    public class Demo{
public class 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));
}
}

filter方法

用于对Stream流中的数据进行过滤

  • Stream<T> filter(Predicate<? super T> predicate);
  • filter方法中的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
  • Predicate中的抽象方法:
    • boolean test(T t)
    public class Demo{
public static void main(String[] args){
// 创建一个Stream流
Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
// 对Stream中的元素进行过滤,只要姓张的人
Stream<String> stream2 = stream.filter((String name)->{return name.stratsWith("张");});
// 遍历Stream2
stream2.forEach(name->System.out.println(name));
/*
Stream流属于管道流,只能被消费(使用)一次
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
IllegalStateException: stream has already been operated upon or closed
*/
// 遍历stream流
stream.forEach(name-> System.out.println(name));
}
}

map方法

用于类型转换

  • 如果需要将流中的元素映射到另一个流中,可以使用map方法
  • <R> Stream<R> map(Function <? super T, ? extends R> mapper);
  • 该接口需要一个Function函数式接口参数,可以将当前流中的T类型转换为另一种R数据的流
  • Function的抽象方法
    • R apply(T t);
    public class Demo{
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));
}
}

count 方法

用于统计Stream流中元素的个数

  • long count();
  • count 方法是一个终结方法,返回值是一个long类型的整数,只能使用一次,使用完之后,不可以使用流的其他方法
    public class Demo{
public static void main(String[] args){
//获取一个Stream流
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Stream<Integer> Stream = list.stream();
long count = stream.count();
System.out.println(count); // 4
}
}

limit方法

用于截取流中的元素

  • limit方法可以对流进行截取,支取前n个
  • Stream<T> limit(long maxSize);
    • 参数是一个int类型。如果当前的长度大于参数则进行截取, 否则不进行截取
  • limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
    public class Demo{
public static void main(String[] args){
// 获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 使用limit放啊进行截取,只要前三个元素
Stream<String> stream2 = stream.limit(3);
// 遍历操作
stream2.forEach(name->System.out.println(name));
}
}

skip方法

用于跳过元素,截取后面的元素

  • 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
  • Stream<T> skip(long n);
    • 如果流的当前长度大于n,则跳过钱n个, 否则得到一个长度为0的空流.
    public class Demo{
public static void main(String[] args){
// 获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 使用skip方法跳过前面三个元素
Stream<String> stream2 = stream.skip(3);
// 遍历stream2
stream2.forEach(name-> System.out.println(name));
}
}

concat方法

用于把流组合在一起

  • 如果有两个流,希望合并成一个流,那么可以使用Stream接口的静态方法concat
  • static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T>b)
    public class Demo{
public static void main(String[] args){
//创建一个Stream流
Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
// 获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream2 = Stream.of(arr);
// 把以上两个流组成为一个流
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(name->System.out.println(name));
}
}

2.方法引用

1.冗余的Lambda

定义一个打印的函数式接口

@FunctionalInterface
public interface Printable {
//定义字符串的抽象方法
void print(String s);
}

打印字符串

public class Demo{
// 定义一个方法,参数传递的是Printable的接口,对字符串进行打印
public static void printString(Printable p){
p.print("hello world");
}
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);
}
}

2.分析

这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out 对象中的 println(String) 方法。既然Lambda希望做的事情就是调用 println(String) 方法,那何必自己手动调 用呢?

3.使用方法引用

public class Demo{
public static void printString(Printable p){
p.print("Hello world");
}
public static void main(String[] args){
// 调用方法使用方法的引用
printString(System.out::println);
}
}

4.方法引用符

双冒号:: 为引用运算符,称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,name我们可以使用双冒号来引用该方法作为lambda的替代者。

语法分析

例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于

printString 方法的函数式接口参数,对比下面两种写法,完全等效:

  • Lambda表达式写法: s -> System.out.println(s);
  • 方法引用写法: System.out::println

第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。

第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全一

样,而第二种方法引用的写法复用了已有方案,更加简洁。

注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常

5.通过对象名引用成员方法

定义类

public class MethodRefObject{
public void printUpperCase(String str){
System.out.println(str.toUpperCase());
}
}

函数式接口定义

@FunctionalInterface
public interface Printable{
void print(String str);
}

那么当需要使用这个printUpperCase成员方法来代替Printable接口的Lambda的时候,已经具有了MethodRefObject类的对象实例。则可以通过对象名引用成员方法,代码:

/*
通过对象名引用成员方法
使用前提:
对象名是已经存在的,成员方法也存在
*/
public class Demo{
// 定义一个方法,方法的参数传递Printable接口
public static void printString(Ptrintable p){
p.print("hello");
}
public static void main(String[] args){
// 创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
printString(obj::printUpperCaseString);
}
}

6.通过类名称引用静态方法

由于在java.lang.Math类中已经存在存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。

函数式接口:

@FunctionalInterface
public interface Calcable{
int calc(int num);
} public class Demo{
public static int method(int number, Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args){
// 调用method方法,传递计算绝对值
int number = method(-10, Math::abs);
System.out.println(number2);
}
}

7.通过super引用成员方法

定义见面的函数式接口

@FunctionalInterface
public interface Greetable{
//定义一个见面的方法
void greet();
}

定义父类

public class Human{
//定义一个sayHello的方法
public void sayHello(){
System.out.println("hello,我是Human");
}
}

定义子类

public class Man extends Human{
//子类重写父类的sayHello方法
@Override
public void sayHello(){
System.out.println("hello 我是Man");
}
//定义一个方法参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
public static void main(String[] args){
new Man().show();
}
}
// hello 我是Human

8.通过this引用成员方法

定义购买的函数式接口

@FunctionalInterface
public interface Richable{
// 定义一个想买什么就买什么的方法
void buy();
}

定义一个类

使用this引用本类的成员方法

public class Husband{
// 定义一个买房子的方法
public void buyHouse(){
System.out.println("北京二环买一套四合院");
}
// 定义一个结婚的方法参数传递Richable接口
public void marry(Richable r){
r.buy();
}
//定义一个非常高兴的方法
public void soHappy(){
marry(this::buyHouse);
}
public static void main(String[] args){
new Husband().soHappy();
}
}

9.类的构造器引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示

定义Person类

public class Person{
private String name;
public Person(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
}

Person对象的函数式接口

@FunctionalInterface
public interface PersonBuilder{
Person builderPerson(String name);
}

Demo

public class Demo{
//定义一个方法,传递的是姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name, PersonBuilder pb){
Person person = pb.builderPersom(name);
System.out.println(person.getName());
}
public static void main(String[] args){
/*构造方法new Person(String name) 已知
创建对象已知 new
就可以使用Person引用new创建对象*/
//使用Person类的带参构造方法,通过传递的姓名创建对象
printName("古力娜扎", Person::new);
}
}

10.数组的构造器引用

数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。

定义一个创建数组的函数式接口

@FunctionalInterface
public class ArrayBuilder{
//定义一个创建int类型数组的方法,参数传递的是数组的长度,返回创建的int类型数组
int[] builderArray(int length);
}

Demo

public class Demo{
/*
定义一个方法
方法的参数传递创建数组的长度和ArrayBuilder接口
方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
*/
public static int[] createArray(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args){
/*
使用方法引用优化Lambda表达式
已知创建的就是int[]数组
数组的长度也是已知的
就可以使用方法引用
int[]引用new,根据参数传递的长度来创建数组
*/
int[] array = createArray(10, int[]::new);
System.out.println(Arrays.toString(array));
System.out.println(array.length);
}
}

Stream和方法引用的更多相关文章

  1. Stream流方法引用

    一.对象存在,方法也存在,双冒号引用 1.方法引用的概念: 使用实例: 1.1先定义i一个函数式接口: 1.2定义一个入参参数列表有函数式接口的方法: 1.3调用这个入参有函数式接口的方法: lamb ...

  2. Java 8新特性:新语法方法引用和Lambda表达式及全新的Stream API

    新语法 方法引用Method references Lambda语法 Lambda语法在AndroidStudio中报错 Stream API 我正参加2016CSDN博客之星的比赛 希望您能投下宝贵 ...

  3. JavaSE复习(七)Stream流和方法引用

    Stream流 全新的Stream概念,用于解决已有集合类库既有的弊端. 传统集合的多步遍历代码 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作.而当我们 ...

  4. Stream流、方法引用

    Stream流.方法引用 Stream流.方法引用 Stream流.方法引用 Stream流.方法引用 Stream流.方法引用 ... ...

  5. Java8特性之Lambda、方法引用以及Stream流

    Java 8 中的 Streams API 详解:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/ Java笔记——Jav ...

  6. JDK1.8新特性(一) ----Lambda表达式、Stream API、函数式接口、方法引用

    jdk1.8新特性知识点: Lambda表达式 Stream API 函数式接口 方法引用和构造器调用 接口中的默认方法和静态方法 新时间日期API default   Lambda表达式     L ...

  7. 2020你还不会Java8新特性?方法引用详解及Stream 流介绍和操作方式详解(三)

    方法引用详解 方法引用: method reference 方法引用实际上是Lambda表达式的一种语法糖 我们可以将方法引用看作是一个「函数指针」,function pointer 方法引用共分为4 ...

  8. java基础第11期——Stream流、方法引用、junit单元测试

    1.Stream流 Stream流与io流是不同的东西,用于解决集合类库已有的弊端, 1.1 获取Stream流: Collection集合的Stream方法,注意Map集合要经过转化 default ...

  9. Java8新特性代码示例(附注释)- 方法引用,Optional, Stream

    /** * java8中的函数式接口,java中规定:函数式接口必须只有一个抽象方法,可以有多个非抽象方法,同时,如果继承实现了 * Object中的方法,那么也是合法的 * <p> * ...

随机推荐

  1. python数据库-MongoDB的安装(53)

    一.NoSQL介绍 1.什么是NoSQL NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL". NoSQL,指的是非关系型的数据库.NoSQL有时也称 ...

  2. java中几个常见的问题

    1.正确使用equals方法 Object的equals方法容易抛出空指针异常,应使用常量或确定有值的对象来调用equals方法 例如: //不能使用一个值为null的引用类型变量来调用非静态方法,否 ...

  3. MyBatis 接口多参数的处理方法

    From<MyBatis从入门到精通> 1.接口类中增加的方法: /* 2.7 多个接口参数的用法 多个参数时,可以选取的方案有:使用Map类型或者使用@Param注解 使用Map类型作为 ...

  4. LoadRunner Community Edition 12.60 无法获取Community License

    更新:该问题于2018/9/28已修复.附邮件: Hi Morris, Thank you for your update. I would like to tell you that we had ...

  5. [记录]NGINX配置HTTPS性能优化方案一则

    NGINX配置HTTPS性能优化方案一则: 1)HSTS的合理使用 2)会话恢复的合理使用 3)Ocsp stapling的合理使用 4)TLS协议的合理配置 5)False Start的合理使用 6 ...

  6. Spark第一周

    Why Scala 在数据集不是很大的时候,开发人员可以使用python.R.MATLAB等语言在单机上处理数据集.但是在大数据时代,数据集少说都是TB.PB级别,此时便需要分布式地处理.相较于上述语 ...

  7. java高并发系列 - 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能

    这是java高并发系列第16篇文章. 本篇内容 介绍CountDownLatch及使用场景 提供几个示例介绍CountDownLatch的使用 手写一个并行处理任务的工具类 假如有这样一个需求,当我们 ...

  8. C语言入门4-运算符和表达式

    一.     分类 C语言一共有34种运算符,10种运算类型,本节我们要掌握的有( 7 种) 算术运算符(+.-.*./.%). 关系运算符(>.>=.==.!=.<.<=). ...

  9. Java EE产生的背景

    为了满足开发多层体系结构的企业级应用的需求,Java公司的创始人Sun公司在早期的J2SE(Java 2 Platform Standard Edition)基础上,针对企业级应用的各种需求,提出了J ...

  10. Java--ASCII码

    ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧 ...