Stream流和函数式编程

本文介绍了Java中的Stream流,强调其不存储数据而按需计算的特点,以及Stream的获取、转换和执行操作的过程。内容包括数据源、Pipelining、内部迭代的概念,以及常用方法如Filter、Map和Limit。此外,还探讨了函数式接口,特别是Lambda表达式的应用,如延迟执行和优化日志案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Stream流

                 拼接流式模型

生产饮料 放瓶子   洗瓶子  装饮料  封口  装箱

java中的Stream并不会存储任何数据,而是按需计算

数据源: 流的来源,可以是集合,数组等.
Pipelining:中间的每一步操作都会返回流对象本身,这样的操作可以串联为一个管道,可以对操作进行优化,比如延迟执行和短路.
内部迭代:以前的方法都是先有集合,在进行迭代(外部迭代),流可以调用foreach()方法直接遍历.(内部迭代)

当使用流的时候,通常包括三个步骤:
获取一个数据源(source) -> 数据转换 -> 执行操作获取想要的结果,每次转换原有Stream不变,返回一个新的Stream对象.


获取流:
1.所有的Conllection集合都可以.stream来获取流(list.stream() )

2.Stream接口的静态方法of可以获取数组对应的流(Stream.of(1,2,3,4) )

常用方法(除了终结方法,其他都是延迟方法)

  • 延迟方法:返回值类型仍为Stream流,支持链式调用.
  • 终结方法:返回值类型不在是Stream流,不支持链式调用(包括count,forEach)

例:

//创建一个list集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");

/**
* Stream流写法 关注的是干什么,而不是怎么干
*/
//对list集合元素进行过滤, 只要张, 只要姓名长度为3的人
list.stream().filter((String name)->{
	return name.startsWith("张");
}).filter((String name)->{
	return name.length()==3;
});
//简化写法
list.stream()
        .filter(name -> name.startsWith("张"))
        .filter(name -> name.length() == 3)
        .forEach(name-> System.out.println(name));

System.out.println("------------------");
/**
* 传统写法
*/
//对list集合元素进行过滤, 只要张
ArrayList<String> a = new ArrayList<>();
for(String s : list) {
    if(s.startsWith("张")) {
        a.add(s);
    }
}
//对a集合进行过滤,只要姓名长度为3的人
ArrayList<String> b = new ArrayList<>();
for(String s : a) {
    if(s.length() == 3) {
        b.add(s);
    }
}
Iterator<String> iterator = b.iterator();
while(iterator.hasNext()) {
    String name = iterator.next();
    System.out.println(name);
}

Stream流属于管道流,只能被消费一次
第一个Stream流调用完毕方法,数据就会流转到下一个流上
而这时第一个流已经使用完毕,就会关闭了
第一个流就不在调用方法了

Filter方法(延迟方法,过滤流)

list.stream()
        .filter(name -> name.startsWith("张"))
        .filter(name -> name.length() == 3)
        .forEach(name-> System.out.println(name));

Map方法(延迟方法,映射)

Stream<Integer> integerStream = stream.map((String s) -> {
    return Integer.parseInt(s);
});

//Count方法 (终结方法,用于统计流中元素的个数)
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);

System.out.println(list.stream().count());

Limit方法(截取流)

Stream<String> stream = Stream.of("张无忌", "赵敏", "张三丰");
Stream<String> limit = stream.limit(2);
limit.forEach(name-> System.out.println(name));

//Skip方法(跳过流的几个元素)
Stream<String> stream = Stream.of("张无忌", "赵敏", "张三丰");

Stream<String> skip = stream.skip(1);
skip.forEach(name-> System.out.println(name));

Concat方法(将两个流合并)

Stream<String> streamA = Stream.of("张无忌", "赵敏", "张三丰");
Stream<String> streamB = Stream.of("喜羊羊", "灰太狼", "美羊羊");
Stream<String> concat = Stream.concat(streamA, streamB);
concat.forEach(name-> System.out.println(name));

集合练习题:

/**
 * Stream流方法
 */
//第一个队伍A只要3个名字的成员,放入新集合
//新集合只要前三个人
Stream<String> newA = listA.stream()
        .filter(name -> name.length() == 3)
        .limit(3);
//第二个队伍只要张姓
//新集合不要前两个人
Stream<String> newB = listB.stream()
        .filter(name -> name.startsWith("张"))
        .skip(2);
//结果集
Stream.concat(newA, newB).map(name->new Person(name)).forEach(p-> System.out.println(p));
 


/**
 * 传统方法
 */
ArrayList<String> newA = new ArrayList<>();
ArrayList<String> newB = new ArrayList<>();
//第一个队伍A只要3个名字的成员,放入新集合
for (String s : listA) {
    if(s.length() == 3) {
        newA.add(s);
    }
}
//新集合只要前三个人
for (int i = 3; i < newA.size(); i++) {
    newA.remove(i);
}

//第二个队伍只要张姓
for (String s : listB) {
    if(s.startsWith("张")){
        newB.add(s);
    }
}

//新集合不要前两个人
int i=0;
while(i < 2) {
    newB.remove(0);
    i++;
}

//结果集
ArrayList<String> result = new ArrayList<>();
for (String s : newA) {
    result.add(s);
}
for (String s : newB) {
    result.add(s);
}

result.forEach(name-> System.out.println(name));

System.out.println("--------------------------------");
//创建对象
ArrayList<Person> people = new ArrayList<>();
for (String s : result) {
    people.add(new Person(s));
}
people.forEach(name-> System.out.println(name));

函数式接口:(只有一个抽象方法的接口)

函数式接口在java中指:有且只有一个抽象方法的接口
java中的函数式接口编程体现就是Lambda

@Override
* 检测方法是否为重写方法
* 是:编译通过
* 否:编译失败
* @FunctionalInterface注解
* 检测这个接口是否为函数式接口
* 是:编译成功
* 否:编译失败  (没有抽象方法 或者抽象方法超过1个)

@FunctionalInterface
public interface MyFunctionInterface {
    /**
    * 定义一个抽象方法
    */
    public abstract void method();

     // void method2();
}

//1.可以传递一个接口
show(new MyFunctionInterfaceImpl());

//2.传递接口的匿名内部类 使用匿名内部类会创建一个class文件
show(new MyFunctionInterface() {
        @Override
        public void method() {
                System.out.println("匿名内部类");
        }
});

//3.使用Lambda表达式
show(()->{
        System.out.println("Lambda表达式");
});

//4.简化Lambda
show(()-> System.out.println("简化"));

使用匿名内部类会创建一个class文件,使用Lambda不会创建,建议使用Lambda表达式.

Lambda表达式的延时执行:有些场景的代码执行后,结果不一定会被使用,会造成性能浪费.而Lambda表达式是延时执行的,可以作为解决方案,提升性能.

性能浪费的日志案例:
日志可以帮我们快速定位问题,记录程序运行过程中的情况,以便项目的优化和监控.
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行输出打印.

~使用Lambda表达式对日志案例进行优化

  • Lambda的特点:延迟加载
  • 使用前提:必须存在函数式接口
//定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口
public static void showLog(int level,MessageBuilder mb) {
    //对日志的等级进行判断,如果为1级,调用方法
    if(level == 1) {
        System.out.println(mb.msgBuilder());
    }
}

public static void main(String[] args) {
//定义三个日志信息
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";

//调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以传递Lambda表达式
showLog(1,()->{
    return msgA + msgB + msgC;
});
/**
* 使用Lambda表达式作为参数传递,仅仅是吧参数传递到ShowLog中,
* 只有满足条件(等级为1)
* 才会调MessageBuilder中的方法进行字符串的拼接
* 条件不满足
* 就不会调用方法拼接
* 就不会造成性能浪费
*/
}

函数式接口作为返回值:
private static Comparator<Integer> getComparator() {

    //方法的返回值是一个接口,可以返回该接口的匿名内部类
    return new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1-o2; //升序
        }
    };
}
//简化写法
private static Comparator<Integer> getCpt() {
        //方法的返回值是一个函数式接口,可以返回Lambda表达式
        return (Integer o1,Integer o2)->{ return o1-o2;};
}

函数式接口作为参数:
public static void startThread(Runnable runnable) {
    //开启线程
    new Thread(runnable).start();
}


public static void main(String[] args) {
    //使用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类.
    startThread(new Runnable() {
        @Override
        public void run() {
                System.out.println(Thread.currentThread().getName()+"线程启动了");
        }
    });

    //调用startThread方法,方法的参数是一个函数式接口,所以可以使用Lambda表达式
    startThread(()->
        System.out.println(Thread.currentThread().getName()+"线程启动了")
    );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值