8---线程深入+Lambda表达式+Stream流

本文详细介绍了Java中线程的深入理解,包括线程安全问题及其解决方案,如同步代码块、同步方法和Lock锁。接着探讨了Lambda表达式的概念、使用格式和优势,并通过实例展示了其简洁的代码风格。最后,文章讲解了Stream流的引入背景、获取方式以及常用操作方法,阐述了如何高效地对集合数据进行处理。

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

1、线程的深入

1、线程安全问题
(1)概念:

​ 多线程操作共享数据,导致共享数据错乱了。

(2)出现该问题的条件

​ 1.有多条线程
​ 2.有共享数据
​ 3.有多条语句操作共享数据

(3)解决方案:

​ 使用同步机制可以解决线程安全问题。(同步机制虽然解决了问题,但是效率将会变低)

​ 1.同步代码块
​ 2.同步方法
​ 3.Lock锁

2、解决方案之同步代码块
(1)使用格式:
synchronized (锁对象) {
    操作共享数据的代码;
}

​ 注意:任何对象都可以作为锁,而且这些锁都是同一把锁。

// 1.定义一个类实现Runnable接口,重写run方法,在run方法中编写卖票逻辑
public class SaleTickets implements Runnable {
    int ticketNumber = 100;
    Object obj = new Object(); // 创建一个对象

    @Override
    public void run() { // 3个线程执行run方法
        // 卖100张票的任务
        // 循环卖票
        String name = Thread.currentThread().getName();
        while (true) {
            // 用户注册
            // 用户登录
            // 用户浏览商品
            // 用户购买, 只锁操作共享数据的代码

            // 必须是同一把锁
            synchronized (obj) {//同步代码块要在while方法内部定义,否则就不会切换线程
            // synchronized (new Object()) {
                // 如果票数大于0,就卖一张票
                if (ticketNumber > 0) {
                    try {
                        Thread.sleep(10); // 此处让它睡一睡是为了更好地让其产生线程安全问题
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticketNumber--;
                    System.out.println(name + "卖了一张票,剩余 " + ticketNumber);
                } else {
                    // 没有票
                    break;
                }
            }
            Thread.yield(); // 尽量让CPU切换到其他线程
        }
    }
}
3、解决方案之同步方法
(1)使用格式
修饰符 synchronized 返回值类型 方法名() {
    操作共享数据的代码;
}
(2)实质:

同步方法相当于方法中所有的内容均在同步代码块内。

​ a、【普通的同步方法】使用this作为锁

​ b、【静态同步方法】使用类名.class作为锁

​ (此时静态方法中没有this(因为使用类名调用))

// 1.定义一个类实现Runnable接口,重写run方法,在run方法中编写卖票逻辑
public class SaleTickets implements Runnable {
    static int ticketNumber = 100;

    @Override
    public void run() { // 3个线程执行run方法
        String name = Thread.currentThread().getName();//此语句一定要在run方法内部
        while (true) {
            // 如果票数大于0,就卖一张票
            saleTicket();
            if (ticketNumber == 0) {
                break;
            }
            Thread.yield(); // 尽量让CPU切换到其他线程
        }
    }

    // 有锁的线程能够进入同步方法,没有锁的线程在外面等待
    public synchronized void saleTicket() {
        if (ticketNumber > 0) {
            try {
                Thread.sleep(10); // 看问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketNumber--;
            String name = Thread.currentThread().getName();
            System.out.println(name + "卖了一张票,剩余 " + ticketNumber);
        }
    }		

以上的同步方法相当于以下的同步代码块

// 同步方法相当于这样同步代码块
public void saleTicket2() {
    synchronized (this) {//这里地同步代码块运用的是调用该方法地对象作为锁
                         //实际上,同步方法就是这样的同步代码块。
        if (ticketNumber > 0) {
            try {
                Thread.sleep(10); // 看问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketNumber--;
            String name = Thread.currentThread().getName();
            System.out.println(name + "卖了一张票,剩余 " + ticketNumber);
        }
    }
}

当以上的方法为静态方法的时候:

public synchronized static void saleTicket4() {
    if (ticketNumber > 0) {
        try {
            Thread.sleep(10); // 看问题
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ticketNumber--;
        String name = Thread.currentThread().getName();
        System.out.println(name + "卖了一张票,剩余 " + ticketNumber);
    }
}

线程执行测试类

public class Demo03 {
    public static void main(String[] args) {
        SaleTickets st = new SaleTickets();
        // 2.创建多个线程,传入接口的实现类对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 3.启动多个线程
        t1.start();
        t2.start();
        t3.start();
    }
}
4、解决方法之Lock
(1)概念:

​ Lock是一个接口,而Lock也有其实现类: ReentrantLock,叫做重入锁

(2)实现类: ReentrantLock

​ 有两个方法:

void lock() 获得锁
void unlock() 释放锁
(3)Lock的使用格式:
lock.lock(); // 获得锁
try {
    操作共享资源的代码
} finally {//保证锁一定要释放
    lock.unlock(); // 释放锁
}

有锁的线程进入,没有锁的线程在外面等待

// 1.定义一个类实现Runnable接口,重写run方法,在run方法中编写卖票逻辑
public class SaleTickets implements Runnable {
    int ticketNumber = 100;
    Lock lock = new ReentrantLock();//多态

    @Override
    public void run() { // 3个线程执行run方法
        // 卖100张票的任务
        // 循环卖票
        String name = Thread.currentThread().getName();
        while (true) {
            // 如果票数大于0,就卖一张票
            // 获得锁, 锁住操作共享资源的代码
            lock.lock();
            try {
                if (ticketNumber > 0) {
                    try {
                        Thread.sleep(10); // 看问题
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticketNumber--;
                    System.out.println(name + "卖了一张票,剩余 " + ticketNumber);
                } else {
                    // 没有票
                    break;
                }
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }
}

线程执行测试类

public class Demo04 {
    public static void main(String[] args) {
        SaleTickets st = new SaleTickets();
        // 2.创建多个线程,传入接口的实现类对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 3.启动多个线程
        t1.start();
        t2.start();
        t3.start();
    }
}
5、线程等待和唤醒
(1)概念

等待和唤醒,通常是两个线程之间的事情,一个线程等待,另外一个线程负责唤醒

(2)方法

​ 作用于线程的等待wait()方法和notify()方法是在Object类中的

​ PS:为什么wait和notify方法放在Object类中?

​ 因为wait和notify,必须使用锁对象调用,而任意对象都可以作为锁

Object类中:
    void wait() 导致当前线程一直等待
    void notify() 唤醒正在等待的单个线程
(3)使用格式:

​ wait()和notify()必须【在同步代码块中】,且使用【锁对象调用】*

(4)sleep和wait的区别
sleep睡眠的时候【不会释放锁】
sleep睡眠时间到,会自动醒来

wait等待的时候会【释放锁】
wait等待了,需要别人唤醒
public static void main(String[] args) throws InterruptedException {
    Object obj = new Object();
    // obj.wait(); // IllegalMonitorStateException: 非法监视状态异常
    
    // 启动一个新的线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (obj) {
                System.out.println("wait前");
                try {
                    obj.wait();
                    System.out.println("wait后");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();

    Thread.sleep(3000);

    // 启动一个新的线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (obj) {
                System.out.println("唤醒前");
                obj.notify();
            }
        }
    }).start();
}

/*
过程分析:
主线程main从上往下执行,执行到调用第一个start()方法时候,第一个线程(简称线程一)被启动。
此时执行第一个同步代码块的线程可能是这两个线程中的一个,由cpu随机分配。
为了让线程一执行同步代码块,我们在start()方法后面让main线程睡眠3s;
线程一 进入第一个同步代码块,拿到锁(obj),向下执行,执行到wait,便交还锁,进入沉睡状态,此时没人叫醒(notify),是不会醒来的
这时主线程main醒来,继续向下执行,执行到调用第二个start()方法时候,第一个线程(简称线程二)被启动。
此时执行第二个同步代码块的线程可能是这两个线程中的一个,由cpu随机分配。
如今假设cpu执行主线程main,其往下执行,执行到最后一个},main方法结束,主线程main结束
线程二 进入第二个同步代码块,拿到锁(obj),【注意,这里两个代码块的锁是同一个锁】
线程二拿着锁向下执行,执行到notify,唤醒了线程一,但是由于锁仍然在线程二中,故线程一仍没办法执行代码块,处于阻塞状态(但不是wait那种状态)
故线程二继续向下执行,执行完其所在的第二个代码块后,交还锁,run方法结束,线程二结束
线程一得到锁后,继续往下执行,执行完其所在的第一个代码块后,交还锁,run方法结束,线程一结束
 */
(5)案例练习
目标:
    包子铺生产一个包子,吃货吃一个包子,包子铺再生产一个包子,
    吃货再吃一个包子.包子铺再生产一个包子,吃货再吃一个包子.

讲解:
    分析:
        1.包子铺
            有包子就等待
            没有包子就生成包子,通知吃货

        2.吃货
            没有包子就等待
            有包子就吃掉,通知包子铺

    实现步骤:
        1.定义BaoZiPu类实现Runnable表示包子铺生产包子的任务
        2.定义ChiHuo类实现Runnable表示吃货吃包子的任务
        3.创建一个线程执行BaoZiPu的任务
        4.创建一个线程执行ChiHuo的任务

包子铺类

// 1.定义BaoZiPu类实现Runnable表示包子铺生产包子的任务
public class BaoZiPu implements Runnable {
    private ArrayList<String> list;

    public BaoZiPu(ArrayList<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 包子铺生产包子的任务
        int num = 1;
        while (true) {
            synchronized (list) {//把list当作锁,这样吃货和包子铺用的就是同一把锁
                // 有包子就等待
                if (list.size() > 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {}
                }

                // 没有包子就生成包子,通知吃货
                list.add("包子" + num);//string + int ---> string
                num++;
                System.out.println("包子铺生产了一个包子: " + list);
                list.notify();
            }
        }
    }
}

吃货类

// 2.定义ChiHuo类实现Runnable表示吃货吃包子的任务
public class ChiHuo implements Runnable {
    private ArrayList<String> list;

    public ChiHuo(ArrayList<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 吃货吃包子的任务
        while (true) {
            synchronized (list) {//把list当作锁,这样吃货和包子铺用的就是同一把锁
                // 没有包子就等待
                if (list.size() == 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {}
                }

                // 有包子就吃掉,通知包子铺
                String baozi = list.remove(0);
                System.out.println("吃货吃掉了: " + baozi);
                list.notify();
            }
        }
    }
}

测试类

public static void main(String[] args) {
        // 制造一个共同的容器,让包子铺和吃货能共同访问。
            //(包子铺往里放包子,吃货往里拿并吃包子)
        ArrayList<String> list = new ArrayList<>();

        BaoZiPu bzp = new BaoZiPu(list);
        ChiHuo ch = new ChiHuo(list);

        // 3.创建一个线程执行BaoZiPu的任务
        Thread t1 = new Thread(bzp);
        t1.start();

        // 4.创建一个线程执行ChiHuo的任务
        Thread t2 = new Thread(ch);
        t2.start();
    }
}
6、线程池
(1)一个程执行一个任务的缺点(线程池登场的背景)

​ 我们创建一个线程执行任务后线程死亡.
​ 如果线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁【创建】线程和【销毁】线程需要时间。

​ 我们最好能让线程重复利用,让线程多干一些活.不要频繁创建和销毁线程.

(2)线程池的概念

​ 线程池就是存放线程的容器

(3)线程池的优点

​ 线程池里面的线程不会销毁,可以重复使用。这样减少了线程的创建和销毁,提高线程的利用率,节省了资源。

(4)线程池的创建方式之Runnable方式

​ 使用Executors类中的一个静态方法newFixedThreadPool(int nThreads)创建线程池。

static ExecutorService newFixedThreadPool(int nThreads) //创建一个具有固定数量的线程的线程池
//int nThreads:线程池中线程的数量
//ExecutorService:就是线程池

​ Runnable方式创建及使用线程池步骤

    1.创建线程池
    2.定义类实现Runnable接口
    3.重写run方法.(任务)
    4.创建实现类对象
    5.提交任务

​ 见如下代码:

public class MyRunnable implements Runnable {
    // 3.重写run方法.(任务)
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "任务执行完成");
    }
}
public static void main(String[] args) {
    // 1.创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(3);
    System.out.println(pool);
//java.util.concurrent.ThreadPoolExecutor@71be98f5[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    
//ExecutorService重写了toString方法,打印出来显示,返回的是Excutors这个接口的一个实现类ThreadPoolExecutor,poolsize:线程池大小,此时为0,是因为虽然赋值了三,但是一开始没有使用它,是不会创建线程的;active threads:已激活的线程;queued tast:进入队列的线程;completed tasks:已完成的任务数

    // 4.创建实现类对象
    MyRunnable mr = new MyRunnable();

    // 5.提交任务
    pool.submit(mr);
    pool.submit(mr);
    pool.submit(mr);
    pool.submit(mr);
    pool.submit(mr);

    // 6.关闭线程池(工作中建议不要做,现在练习中,不关闭的话程序不会停止55)
    pool.shutdown();
}
(5)线程池的创建方式之Callable方式

​ 1、Callable和Runnable类似,是一个接口。如下:

public interface Callable<V> {
    V call() throws Exception;
}

​ 2、与Runnable的区别(主要区别在于call()和run()方法)

​ 1.相比于run()方法,其成员方法call()可以抛异常
​ 2.相比于run()方法,其成员方法call()有返回值

​ 3、使用步骤:

1.创建线程池
2.定义类实现Callable
3.重写call方法
4.创建实现类对象
5.提交任务

​ 4、如何拿到Callable的返回值?

​ 通过Future对象的get方法得到返回值

// 2.定义类实现Callable
public class MyCallable implements Callable<Integer> {
    int num;

    public MyCallable(int num) {
        this.num = num;
    }
	//重写call()方法
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
        }
        return sum;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 1.创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(3);

    // 4.创建实现类对象
    MyCallable mc = new MyCallable(100);

    // 5.提交任务
    //mc.call();
// 我们调用的submit提交任务,到时候由线程去调用call方法,我们不是直接调用call方法,不能直接得到返回值
    pool.submit(mc);
    pool.submit(mc);
    pool.submit(mc);
    pool.submit(mc);
    // 当我们调用submit提交任务,到时候由线程去调用call方法线程会把call方法的返回值放到Future中.(Future是一个接口)
    Future<Integer> future = pool.submit(mc);
    // 我们通过Future对象就可以得到<T>结果(future.get())
    Integer re = future.get();//Alt + Enter 处理异常---->throws ExecutionException, InterruptedException
    System.out.println(re);//5050
    
    pool.shutdown();
}
7、死锁

(1)概念:

​ 多线程操作共享资源,导致多个线程相互等待,程序无法向下执行。

(2)产生条件

​ 1.有多个线程
​ 2.有多把锁
​ 3.有同步代码块嵌套

(3)如何避免死锁:
破坏死锁的其中一个条件

可能造成死锁情况示例:

public class DieLockRunnable implements Runnable {
    // 2.有多把锁
    Object objA = new Object();
    Object objB = new Object();

    @Override
    public void run() { // 有2个线程来执行
        // 3.有同步代码块嵌套
        synchronized (objA) {
            System.out.println("1 objA");
            synchronized (objB) {
                System.out.println("1 objB");
            }
        }

        synchronized (objB) {
            System.out.println("2 objB");
            synchronized (objA) {
                System.out.println("2 objA");
            }
        }
    }
}
public class Demo111 {
    public static void main(String[] args) {
        DieLockRunnable dlr = new DieLockRunnable();

        new Thread(dlr).start();
        new Thread(dlr).start();
    }
}

2、函数是编程思想引入-------Lambda表达式

1、引入背景:

​ 面向对象语法的弊端:代码冗余

例如:
    1.定义类(成员变量, 成员方法...)
    2.创建对象
    3.使用对象
2、概念:

​ 函数式编程思想,关注做什么,把我们要做的事情放到一个lambda表达式中(函数)

3、Lambda表达式使用格式:
【方法不用写修饰符,返回值类型,方法名】
格式:
() -> {

}
注意:
	(): 参数列表
	{}: 方法体
	->: 没有实际含义,表示指向
4、Lambda表达式使用前提
1.参数或变量类型是接口
2.接口中只能有一个抽象方法
public class Demo15 {
    public static void main(String[] args) {
        test01(() -> {
        });

        // Swimmable s = () -> {};
    }

    public static void test01(Swimmable x) {//此处的参数类型,无论是普通类型如int x 或者抽象类型 Person x,都无法转成Lambda表达式

    }
}

/*abstract class Person {
    public abstract void show();
}*/

// 只有一个抽象方法的接口称为函数式接口
@FunctionalInterface
interface Swimmable {
    public abstract void swimming();
    // public abstract void swimming22();//只能由一个抽象方法才可以使用Lambda表达式
}
5、优势:

​ Lambda表达式相当于 替换 匿名内部类重写方法,简化了代码,简化代码。

代码示例:

public static void main(String[] args) {
    // Thread(Runnable target);
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("新的线程执行啦");
        }
    });
    t1.start();

    // 换成Lambda表达式
    //Lambda表达式相当于 替换 匿名内部类重写方法,简化了代码
    Thread t2 = new Thread(() -> {
        System.out.println("我是Lambda");
    });
    t2.start();
}
6、Lambda表达式的 有参有返回格式 + 省略格式
(1)有参返回格式:
(数据类型 变量名) -> {
    return 结果;
}
(2)省略格式:
1.【参数类型】可以省略,如: (Person p1, Person p2)--->省略后 (p1, p2)
2.如果只有一个参数,【()】可以省略,如: (Person p1)--->省略后 p1
3.如果{}只有一句代码,可以同时省略【return】和【{}】和【;】。(若要省略则三者必须同时省略)
(3)代码示例
public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(33);
    list.add(11);
    list.add(22);
    list.add(55);

    // Lambda
    /*Collections.sort(list, (Integer o1, Integer o2) -> {
        return o1 - o2;
    });*/

    // 省略Lambda
    Collections.sort(list, (o1, o2) -> o1 - o2);

    System.out.println(list);
}

3、Stream流

1、引入背景:体验集合操作的弊端

​ 每次完成一个需求【都需要遍历集合】,找到满足要求的数据,放到新的集合中.这个过程很繁琐

代码示例

public class Demo16 {
    public static void main(String[] args) {
        // 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
        // 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");

        // 1.拿到所有姓张的
        ArrayList<String> zhangList = new ArrayList<>(); // "张无忌", "张强", "张三丰"
        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }

        // 2.拿到名字长度为3个字的
        ArrayList<String> threeList = new ArrayList<>(); // "张无忌", "张三丰"
        for (String name : zhangList) {
            if (name.length() == 3) {
                threeList.add(name);
            }
        }

        // 3.打印这些数据
        for (String name : threeList) {
            System.out.println(name); // "张无忌", "张三丰"
        }
    }
}
2、概念:
对比了解Stream流

​ 集合主要用于【存储数据】

​ Stream流Stream相当于流水线,用于【对集合数据进行加工和处理】

3、获取Stream流的方式之Collection中的方法stream():
Collection的方法:
    Stream<E> stream() 得到集合的Stream流
使用格式:
// 1.Collection的方法: Stream<E> stream() 得到集合的Stream流

//ArrayList集合
ArrayList<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();

//LinkedList集合
LinkedList<Integer> linked = new LinkedList<>();
Stream<Integer> stream2 = linked.stream();

//HashSet集合
HashSet<String> hs = new HashSet<>();
Stream<String> stream3 = hs.stream();

//HashMap集合【特殊】
HashMap<String, Integer> hm = new HashMap<>();
Stream<String> stream4 = hm.keySet().stream(); // map键的集合--->Stream流
Stream<Integer> stream5 = hm.values().stream();//value值的集合--->Stream流
Stream<Map.Entry<String, Integer>> stream6 = hm.entrySet().stream();//键值对集合-->Stream流
4、获取Stream流的方式之Stream类的静态方法of():
static Stream of(T... values)//可变参数
// Stream的静态方法
Stream<String> stream7 = Stream.of("a", "b", "c");

String[] arr = new String[] {"aa", "bb", "cc"};
Stream<String> stream8 = Stream.of(arr);

Integer[] arr2 = new Integer[] {11, 22, 33};
Stream<Integer> stream9 = Stream.of(arr2);

// 基本类型的数组不行
//Stream尖括号的类型表明是要处理的集合/数组内元素的类型,若<>内放如基本类型数组类型,
// 转成Stream之后,操作的并不是基本数据类型数组内部的元素,而是操作数组去了。
//int[] arr3 = new int[] {11, 22, 33};
//Stream<int[]> stream10 = Stream.of(arr3);//没有报错,但是不可以这么写
5、Stream流的常用方法
(1)常用方法
方法名     方法作用        方法种类        是否支持链式调用
count       统计个数        终结                  否
forEach     逐一处理        终结                  否
filter      过滤             函数拼接            是
limit       取用前几个       函数拼接            是
skip        跳过前几个       函数拼接            是
map         映射              函数拼接            是
concat      组合              函数拼接            是
我们发现Stream中的方法返回值分成两种,一种返回值还是Stream,另外一种返回值不是Stream
返回值还是Stream,【函数拼接方法】
返回值不是Stream,【终结方法】
(2)注意事项
1.Stream流只能使用一次
2.Stream流操作后返回的是新的流
3.Stream流如果不调用终结方法,中途的操作是不会执行的
(3)Stream的foreach方法

​ 遍历流中的每个数据

List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

    // 1.得到Stream流
    Stream<String> stream = one.stream();

    // 2.调用Stream流的方法, 遍历流中的数据
    /*stream.forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });*/

    /*stream.forEach((String s) -> {
        System.out.println(s);
    });*/

    stream.forEach(s -> System.out.println(s));
}
(4)Stream的filter方法

​ 过滤流中的数据,得到满足要求的数据

public static void main(String[] args) {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

    // 1.得到Stream
    Stream<String> stream = one.stream();

    // 2.调用Stream的方法
    /*long count = stream.filter(new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.length() == 2;
        }
    }).count();*/

    /*long count = stream.filter((String s) -> {
        return s.length() == 2;
    }).count();*/

    long count = stream.filter(s -> s.length() == 2).count();

    System.out.println(count);
}
(5)Stream的limit方法

​ limit:限制,取前几个数据

public static void main(String[] args) {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

    /*one.stream().limit(3).forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });*/

    /*one.stream().limit(3).forEach((String s) -> {
        System.out.println(s);
    });*/

    one.stream().limit(3).forEach(s -> System.out.println(s));
}
(6)Stream的skip方法

​ 跳过,跳过前几条数据

public static void main(String[] args) {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

    one.stream().skip(2).forEach(s -> System.out.println(s));
}
(7)Stream的map方法

​ map: 映射,将一种类型的流转换成另一种类型的流

public static void main(String[] args) {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

    Stream<String> stream = one.stream();

    Stream<Student> stream1 = stream.map(new Function<String, Student>() {
        @Override
        public Student apply(String s) {
            // 参数得到的是字符串,方法中将得到的字符串变成了Student,并返回Student对象
            return new Student(s);
        }
    });

    stream1.forEach(s -> System.out.println(s));
}
(8)Stream的concat方法
Stream中: 静态方法
    static Stream concat(Stream<T> a, Stream<T> b) 将两个流合并成一个流
public static void main(String[] args) {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥");

    List<String> two = new ArrayList<>();
    Collections.addAll(two,  "苏星河", "老子", "庄子", "孙子");

    Stream<String> stream1 = one.stream();
    Stream<String> stream2 = two.stream();

    // static Stream concat(Stream<T> a, Stream<T> b) 将两个流合并成一个流
    Stream<String> stream3 = Stream.concat(stream1, stream2);

    /*stream3.forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });*/

    stream3.forEach(s -> System.out.println(s));

    // 老的流不能使用啦
    // stream1.count();
}
(8)使用Stream方式完成案例
public static void main(String[] args) {
    List<String> one = new ArrayList<>();
    Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公", "乔大峰", "欧阳锋");

    List<String> two = new ArrayList<>();
    Collections.addAll(two, "古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");

    // 1.第一个队伍只要名字为3个字的成员姓名;
   /* // 2.第一个队伍筛选之后只要前3个人;
    Stream<String> stream1 = one.stream().filter(new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.length() == 3;
        }
    }).limit(3);

    // 3.第二个队伍只要姓张的成员姓名;
    // 4.第二个队伍筛选之后不要前2个人;
    Stream<String> stream2 = two.stream().filter(new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.startsWith("张");
        }
    }).skip(2);

    // 5.将两个队伍合并为一个队伍;
    Stream<String> stream3 = Stream.concat(stream1, stream2);
    // 6.根据姓名创建 Person 对象;
    Stream<Person> stream4 = stream3.map(new Function<String, Person>() {
        @Override
        public Person apply(String s) {
            return new Person(s);
        }
    });

    // 7.打印整个队伍的Person对象信息。
    stream4.forEach(new Consumer<Person>() {
        @Override
        public void accept(Person person) {
            System.out.println(person);
        }
    });*/

    // 1.第一个队伍只要名字为3个字的成员姓名;
    // 2.第一个队伍筛选之后只要前3个人;
    Stream<String> stream1 = one.stream().filter(s -> s.length() == 3).limit(3);

    // 3.第二个队伍只要姓张的成员姓名;
    // 4.第二个队伍筛选之后不要前2个人;
    Stream<String> stream2 = two.stream().filter(s -> s.startsWith("张")).skip(2);

    // 5.将两个队伍合并为一个队伍;
    Stream<String> stream3 = Stream.concat(stream1, stream2);
    // 6.根据姓名创建 Person 对象;
    Stream<Person> stream4 = stream3.map(s -> new Person(s));

    // 7.打印整个队伍的Person对象信息。
    stream4.forEach(person -> System.out.println(person));
}
(9)对Stream流操作结果进行收集

​ 1、背景:

​ 我们使用Stream可以对数据进行处理,但我们处理后通常都是要使用count,forEach这两个终结方法来获取结果,但是没有办法收集处理后的数据

​ Stream流本身不保存数据,于是我们想将Stream操作后的数据【重新存储到集合或数组中(收集数据)】。毕竟集合方便存储数据,Stream方便加工操作数据。

​ 2、将Stream流结果收集到集合中

stream.collect(Collectors.toList());
stream.collect(Collectors.toSet());

​ 3、将Stream流结果收集到数组中

stream.toArray();

​ 4、代码演示

public static void main(String[] args) {
    Stream<String> stream = Stream.of("aa", "bb", "cc");

    // 将Stream流结果收集到集合中
    // List<String> list = stream.collect(Collectors.toList());
    // System.out.println(list);
    // Set<String> set = stream.collect(Collectors.toSet());
    // System.out.println(set);

    // 将Stream流结果收集到数组中
    Object[] objects = stream.toArray();
    for (Object object : objects) {
        System.out.println(object);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值