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