Stream和方法引用

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); } }

猜你喜欢

转载自www.cnblogs.com/liudemeng/p/11363770.html
今日推荐