Java多线程高级特性(JDK8)

您所在的位置:网站首页 apple12参数 Java多线程高级特性(JDK8)

Java多线程高级特性(JDK8)

2023-04-25 13:11| 来源: 网络整理| 查看: 265

[TOC]

一、Java多线程 1.Java多线程基础知识

Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

2.Java线程的生命周期 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 阻塞(BLOCKED):表示线程阻塞于锁。 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。 其他阻塞:通过调用线程的Sleep()或Join发出I/O请求,线程就会进入到阻塞状态,当Sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程就会进入就绪状态。 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

终止(TERMINATED):表示该线程已经执行完毕。

线程生命周期 3.线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

4.创建线程

Java创建线程提供了以下三种方法:

通过实现Runnable接口 通过继承Thread类本身 通过Callable和Future创建线程 4.1 通过实现Runnable接口来创建线程

通过Runnable接口需要重写run() 方法

public void run()

在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。 Thread 定义了几个构造方法,下面的这个是我们经常使用的:

Thread(Runnable threadOb,String threadName);

这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。

新线程创建之后,你调用它的 start() 方法它才会运行。

void start(); 4.2 通过继承Thread来创建线程

创建一个线程的第二种方法是创建一个新的类,该类继承Thread类,然后创建一个该类的实例。 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。

Thread方法:

序号 方法描述 1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。 4 public final void setPriority(int priority) 更改线程的优先级。 5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 7 public void interrupt() 中断线程。 8 public final boolean isAlive()

Thread静态类方法

序号 方法描述 1 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 3 public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 4 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 5 public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 4.3 通过Callable和Future创建线程 创建 Callable 接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 创建 Callable 实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该 Callable 对象的 call() 方法的返回值。 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。 For Example: import java.util.concurrent.*; public class Test { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Task task = new Task(); FutureTask futureTask = new FutureTask(task); executorService.submit(futureTask); executorService.shutdown(); System.out.println("主线程在执行任务..."); try { Thread.sleep(2000); } catch (InterruptedException ex) { ex.printStackTrace(); } try { System.out.println("task运行结果:"+futureTask.get()); } catch (InterruptedException ex) { ex.printStackTrace(); } catch (ExecutionException ex) { ex.printStackTrace(); } System.out.println("所有任务执行完毕"); } } class Task implements Callable{ @Override public Integer call() throws Exception { System.out.println("子线程在执行任务..."); //模拟任务耗时 Thread.sleep(5000); return 1000; } } 5.线程的取消和中断 不安全的取消 Stop()/suspend()/resume()是过期的API,容易导致死锁或者数据不一致 安全的终止线程 interrupt() 中断线程,本质是将线程的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定 isInterrupted() 线程检查自己的中断标志位 静态方法Thread.interrupted() 将中断标志位复位为false 6.常用的方法理解 run()和start()

run就是一个普通的方法,跟其他类的实例方法没有任何区别。

Sleep 不会释放锁,所以我们在用sleep时,要把sleep放在同步代码块的外面。 yield() 当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中,不会释放锁。 wait()和 notify()/notiyfAll()

调用以前,当前线程必须要持有锁,调用了wait() notify()/notiyfAll()会释放锁。

通知机制: 线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程(比如线程A) notify() 唤醒一个线程,唤醒哪一个完全看cpu的心情(谨慎使用)

notiyfAll() 所有在对象O上wait的线程全部唤醒(应该用notiyfAll())

6.线程关键字 6.1 volatile

多个线程同时访问一个共享的变量的时候,每个线程的工作内存有这个变量的一个拷贝,变量本身还是保存在共享内存中。 Violate修饰字段,对这个变量的访问必须要从共享内存刷新一次。最新的修改写回共享内存。可以保证字段的可见性。绝对不是线程安全的,没有操作的原子性。 适用场景:

1、一个线程写,多个线程读;

2、volatile变量的变化很固定

public class VolatileThread implements Runnable { private volatile int a= 0; @Override public void run() { // synchronized (this){ a=a+1; System.out.println(Thread.currentThread().getName()+"----"+a); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } a=a+1; System.out.println(Thread.currentThread().getName()+"----"+a); // } } } public class VolatileTest { public static void main(String[] args) { VolatileThread volatileThread = new VolatileThread(); Thread t1 = new Thread(volatileThread); Thread t2 = new Thread(volatileThread); Thread t3 = new Thread(volatileThread); Thread t4 = new Thread(volatileThread); t1.start(); t2.start(); t3.start(); t4.start(); } } 6.2 synchronized

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。 Synchronized的类锁和对象锁,本质上是两把锁,类锁实际锁的是每一个类的class对象。对象锁锁的是当前对象实例。

public class InstanceAndClass { //测试类锁 private static class TestClassSyn extends Thread{ @Override public void run() { System.out.println("TestClass is going..."); synClass(); } } //测试对象锁 private static class TestInstanceSyn extends Thread{ private InstanceAndClass instanceAndClass; public TestInstanceSyn(InstanceAndClass instanceAndClass) { this.instanceAndClass = instanceAndClass; } @Override public void run() { System.out.println("TestInstance is going..."+instanceAndClass); instanceAndClass.synInstance(); } } //测试对象锁 private static class TestInstance2Syn implements Runnable{ private InstanceAndClass instanceAndClass; public TestInstance2Syn(InstanceAndClass instanceAndClass) { this.instanceAndClass = instanceAndClass; } @Override public void run() { System.out.println("TestInstance2 is going..."+instanceAndClass); instanceAndClass.synInstance2(); } } //锁对象的方法 private synchronized void synInstance(){ SleepUtils.second(3); System.out.println("synInstance is going..."); SleepUtils.second(3); System.out.println("synInstance ended"); } //锁对象的方法 private synchronized void synInstance2(){ SleepUtils.second(3); System.out.println("synInstance2 going..."); SleepUtils.second(3); System.out.println("synInstance2 ended"); } //锁类的方法 private static synchronized void synClass(){ SleepUtils.second(1); System.out.println("synClass going..."); SleepUtils.second(1); } public static void main(String[] args) { InstanceAndClass instanceAndClass = new InstanceAndClass(); Thread t1 = new TestClassSyn(); Thread t2 = new Thread(new TestInstanceSyn(instanceAndClass)); Thread t3 = new Thread(new TestInstance2Syn(instanceAndClass)); t2.start(); t3.start(); SleepUtils.second(1); t1.start(); } } 6.3等待和通知机制 等待方原则:

1、获取对象锁

2、如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足

3、条件满足以后,才能执行相关的业务逻辑

Synchronized(对象){ While(条件不满足){ 对象.wait() } 业务逻辑处理 } 通知方原则

1、获得对象的锁; 2、 改变条件; 3、 通知所有等待在对象的线程

Synchronized(对象){ 业务逻辑处理,改变条件 对象.notify/notifyAll } 6.4管道输入输出流

文件输入输出,网络输入输出,管道输入输出流用于线程中间的数据传递,传输媒介的内存 pipedOutputStream/input 面向的字节

pipedReader/Writer 面向的是字符

只适合线程间一对一的通信,适用范围较狭窄。

public class PipeTransfer { private static class Print implements Runnable{ private PipedReader in; public Print(PipedReader in) { this.in = in; } @Override public void run() { int receive =0; try { while((receive=in.read())!=-1){ System.out.println((char) receive); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { PipedWriter out = new PipedWriter(); PipedReader in = new PipedReader(); //必须进行连接 out.connect(in); Thread t1 = new Thread(new Print(in),"PrintThread"); t1.start(); int receive =0; try { while((receive=System.in.read())!=-1){ out.write(receive); } } catch (IOException e) { e.printStackTrace(); }finally { out.close(); } } } 6.5 join方法

线程A,执行了thread.join(),线程A等待thread线程终止了以后,A在join后面的语句才会继续执行

6.6 一些有用的方法

很多时候我们希望在元素不存在时插入元素,我们一般会像下面那样写代码

synchronized(map){ if (map.get(key) == null){ return map.put(key, value); } else{ return map.get(key); } }

putIfAbsent(key,value)方法原子性的实现了同样的功能

V putIfAbsent(K key, V value)

如果key对应的value不存在,则put进去,返回null。否则不put,返回已存在的value。 boolean remove(Object key, Object value) 如果key对应的值是value,则移除K-V,返回true。否则不移除,返回false。 boolean replace(K key, V oldValue, V newValue) 如果key对应的当前值是oldValue,则替换为newValue,返回true。否则不替换,返回false。

二、并发工具和并发容器 1.Hash

散列,任意长度的输入,通过一种算法,变换成固定长度的输出。属于压缩的映射。Md5,Sha,取余都是散列算法,ConcurrentHashMap中是wang/jenkins算法

2.ConcurrentHashMap

在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

3.ConcurrentHashMap在JDK1.7中的实现

分段锁的设计思路

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment实际是一种可重入锁(ReentrantLock),HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。

ConcurrentHashMap

ConcurrentHashMap初始化方法是通过initialCapacity、loadFactor和concurrencyLevel(参数concurrencyLevel是用户估计的并发级别,就是说你觉得最多有多少线程共同修改这个map,根据这个来确定Segment数组的大小concurrencyLevel默认是DEFAULT_CONCURRENCY_LEVEL = 16;)。

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,可以看到其中的对象属性要么是final的,要么是volatile的。

4.ConcurrentHashMap在1.8下的实现

改进一:取消segments字段,直接采用transient volatile HashEntry[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。 改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。

Table数组+单向链表+红黑树 5.ConcurrentSkipListMap和ConcurrentSkipListSet

ConcurrentSkipListMap TreeMap的并发实现 有序Map ConcurrentSkipListSet TreeSet的并发实现 有序Set

6.SkipList

二分查找和AVL树查找 二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存。这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的移动元素了。

如果需要的是一个能够进行二分查找,又能快速添加和删除元素的数据结构,首先就是二叉查找树,二叉查找树在最坏情况下可能变成一个链表。

于是,就出现了平衡二叉树,根据平衡算法的不同有AVL树,B-Tree,B+Tree,红黑树等,但是AVL树实现起来比较复杂,平衡操作较难理解,这时候就可以用SkipList跳跃表结构。

传统意义的单链表是一个线性结构,向有序的链表中插入一个节点需要O(n)的时间,查找操作需要O(n)的时间。

跳表

如果我们使用上图所示的跳跃表,就可以减少查找所需时间为O(n/2),因为我们可以先通过每个节点的最上面的指针先进行查找,这样子就能跳过一半的节点。

比如我们想查找19,首先和6比较,大于6之后,在和9进行比较,然后在和12进行比较......最后比较到21的时候,发现21大于19,说明查找的点在17和21之间,从这个过程中,我们可以看出,查找的时候跳过了3、7、12等点,因此查找的复杂度为O(n/2)。

跳跃表其实也是一种通过“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。 跳跃表又被称为概率,或者说是随机化的数据结构,目前开源软件 Redis 和 lucence都有用到它。

7.ConcurrentLinkedQueue 无界非阻塞队列

常用方法:

Add,offer:添加元素

Peek:get头元素并不把元素拿走

poll():get头元素把元素拿走

8.CopyOnWriteArrayList和CopyOnWriteArraySet

写的时候进行复制,可以进行并发的读。

适用读多写少的场景:比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

弱点:内存占用高,数据一致性弱

Tips 多用isEmpty()少用 Size()

9.Lock

Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

1.接口类 public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); } lock 获取锁 Lock lock =new ReentrantLock(); # ReetrantLock 可重入锁 lock.lock(); try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } tryLock() 尝试获取锁 返回类型: boolean Lock lock = ...; if(lock.tryLock()) { try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } }else { //如果不能获取锁,则直接做其他事情 } lockInterruptibly() 线程中断 三、阻塞对列

队列满额,线程数据无法插入,就会被阻塞等待数据插入,直到队列可以插入为止,当队列为空时,会被阻塞,直到数据插入后才可以唤醒线程移除。

1.常用的方法 方法 抛出异常 返回值 一直阻塞 超时退出 插入 Add offer put offer 移除 remove poll take poll 检查 element peek 没有 没有 2.常用阻塞队列 ArrayBlockingQueue

数组结构组成有界阻塞队列,先进先出原则,初始化必须传大小,take和put时候用的同一把锁。

LinkedBlockingQueue

链表结构组成的有界阻塞队列,先进先出原则,初始化可以不传大小,put,take锁分离

PriorityBlockingQueue

支持优先级排序的无界阻塞队列,自然顺序升序排列,更改顺序:类自己实现compareTo()方法,初始化PriorityBlockingQueue指定一个比较器Comparator

DelayQueue

使用了优先级队列的无界阻塞队列,支持延时获取,队列里的元素要实现Delay接口。DelayQueue非常有用,可以将DelayQueue运用在以下应用场景:

缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。还有订单到期,限时支付等.

SynchronousQueue

不存储元素的阻塞队列,每个put操作必须要等take操作

LinkedTransferQueue

链表结构组成的无界阻塞队列,Transfer,tryTransfer,生产者put时,当前有消费者take,生产者直接把元素传给消费者

LinkedBlockingDeque

链表结构组成的双向阻塞队列,可以在队列的两端插入和移除,xxxFirst头部操作,xxxLast尾部操作。工作窃取模式。

3.阻塞队的实现原理

Java 阻塞队列使用 Condititon、Lock,在元素进入使用等待wait(),出队使用唤醒。

4.For/Join框架 kyijQs.png

把大任务拆分成很多的小任务,汇总每个小任务的结果得到大任务的结果。 Fork/Join使用两个类来完成以上两件事情。

①ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务 中执行fork()和join()操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了以下两个子类。 ·RecursiveAction:用于没有返回结果的任务。 ·RecursiveTask:用于有返回结果的任务。

②ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行。 Fork/Join有同步和异步两种方式。

5.CountDownLatch

允许一个或多个线程等待其他线程完成操作。CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。

6.CyclicBarrier

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。 CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。

CyclicBarrier和CountDownLatch的区别

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重 置,CountDownLatch.await一般阻塞主线程,所有的工作线程执行countDown,而CyclicBarrierton通过工作线程调用await从而阻塞工作线程,直到所有工作线程达到屏障。

四 、Java 8高级特性 1、Lambda 表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

Style 1 无参,无返回值,Lambda只需要一条语句 Runnable runnable = () -> System.out.println("hello Lambda"); Style 2 一个参数 Consumer con = (t) -> System.out.println(t); Style 3 参数省略括号 Consumer con = t -> System.out.println(t); Style 4 2个参数,具有返回值 Comparator comparator = (x,y) -> { System.out.println("相加结果是:"+(x+y)); return Integer.compare(x,y); }; Style 5 省略return和大括号 Comparator comparator = (x,y) -> Integer.compare(x,y); Style 6 类型推断写法 Comparator comparator = (x,y) -> Integer.compare(x,y); 2.Stream API

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

创建Stream

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

default Stream stream() : #返回一个顺序流 default Stream parallelStream() : # 返回一个并行流 3.Stream 中间操作

原始集合:

List appleList = new ArrayList();//存放apple对象集合 Apple apple1 = new Apple(1,"苹果1",new BigDecimal("3.25"),10); Apple apple12 = new Apple(1,"苹果2",new BigDecimal("1.35"),20); Apple apple2 = new Apple(2,"香蕉",new BigDecimal("2.89"),30); Apple apple3 = new Apple(3,"荔枝",new BigDecimal("9.99"),40); appleList.add(apple1); appleList.add(apple12); appleList.add(apple2); appleList.add(apple3); List转Map /** * List -> Map * 需要注意的是: * toMap 如果集合对象有重复的key,会报错Duplicate key .... * apple1,apple12的id都为1。 * 可以用 (k1,k2)->k1 来设置,如果有重复的key,则保留key1,舍弃key2 */ Map appleMap = appleList.stream().collect(Collectors.toMap(Apple::getId, a -> a,(k1,k2)->k1)); {1=Apple{id=1, name='苹果1', money=3.25, num=10}, 2=Apple{id=2, name='香蕉', money=2.89, num=30}, 3=Apple{id=3, name='荔枝', money=9.99, num=40}} 分组 //List 以ID分组 Map Map groupBy = appleList.stream().collect(Collectors.groupingBy(Apple::getId)); System.err.println("groupBy:"+groupBy); {1=[Apple{id=1, name='苹果1', money=3.25, num=10}, Apple{id=1, name='苹果2', money=1.35, num=20}], 2=[Apple{id=2, name='香蕉', money=2.89, num=30}], 3=[Apple{id=3, name='荔枝', money=9.99, num=40}]} 过滤 filter //过滤出符合条件的数据 List filterList = appleList.stream().filter(a -> a.getName().equals("香蕉")).collect(Collectors.toList()); System.err.println("filterList:"+filterList); [Apple{id=2, name='香蕉', money=2.89, num=30}] 求和 # BigDecimal: BigDecimal totalMoney = appleList.stream().map(Apple::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add); System.err.println("totalMoney:"+totalMoney); //totalMoney:17.48 # Integer int sum = appleList.stream().mapToInt(Apple::getNum).sum(); System.err.println("sum:"+sum); //sum:100


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3