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. 《ElasticSearch6.x实战教程》正式推出(附图书抽奖)

    经过接近1个月的时间,ElasticSearch6.x实战教程终于成册.这本实战教程小册有很多不足(甚至可能有错误),也是第一次完整推出一个系列的教程. 1年前,我开始真正接触ES,在此之前仅停留在知 ...

  2. redux、react-redux、redux-thunk、redux-saga使用及dva对比

    一.redux使用 Redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree ...

  3. [原创]MySQL数据库查询和LVM备份还原学习笔记记录

    一.查询语句类型: 1)简单查询 2)多表查询 3)子查询 4)联合查询 1)简单查询: SELECT * FROM tb_name; SELECT field1,field2 FROM tb_nam ...

  4. Oracle 开发使用笔记一

    1 前段时间换了新公司,工作一直很忙,没什么时间做总结! 关于几个知识点简单做下总结: 1绑定变量的使用: 1)使用几次,在后面的using中要声明几次,使用的顺序要对应声明的顺序 2 存储过程中执行 ...

  5. 个人永久性免费-Excel催化剂功能第32波-空行空列批量插入和删除

    批量操作永远是效率提升的王道,也是Excel用户们最喜欢能够实现的操作虽说有些批量操作不一定合适Excel的最佳实践操作,但万千世界,无奇不有,特别是在国人眼中领导最大的等级森严的职场环境下.Exce ...

  6. 人民网基于FISCO BCOS区块链技术推出“人民版权”平台

    FISCO BCOS是完全开源的联盟区块链底层技术平台,由金融区块链合作联盟(深圳)(简称金链盟)成立开源工作组通力打造.开源工作组成员包括博彦科技.华为.深证通.神州数码.四方精创.腾讯.微众银行. ...

  7. JWT(JSON WEB TOKEN)实例

    JWT的工具类 加密解密工具 package top.wintp.crud.util; import com.auth0.jwt.JWTSigner; import com.auth0.jwt.JWT ...

  8. AppBoxFuture: 二级索引及索引扫描查询数据

      数据库索引对于数据查询的重要性不可言喻,因此作者在存储层实现了二级索引,以及利用索引进行扫描的功能.目前仅实现了分区表与非分区表的本地索引(数据与索引共用一个Raft组管理),全局索引及反向索引待 ...

  9. python中的内存机制

    首先要明白对象和引用的概念 (例子:a=1, a为引用,1为对象,对象1的引用计数器为1,b=1此时内存中只有一个对象1,a,b都为引用,对象的引用计数器此时为2,因为有两个引用) a=1,b=1 i ...

  10. Pinyin4j简单使用教程

    Pinyin4j是一个流行的Java库,支持中文字符和拼音之间的转换,拼音输出格式可以定制,在项目中经常会遇到需求用户输入汉字后转换为拼音的场景,这时候Pinyin4j就可以派上用场 有自己私服的可以 ...