我们先学习两组概念。首先是并发与并行
其次,进程和线程的区别是:
进程:一个内存中运行的一个应用程序就是一个进程。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程。进程也是程序的一次执行过程,是系统运行程序的基本单位。系统运行一个程序,就是一个进程从创建,到运行,消亡的过程
所有的应用程序都需要进到内存中执行(临时存储RAM)。进入到内存的程序叫进程。结束进程就是将进程从内存中清除了
线程:是进程中的一个执行单元,负责当前进程中程序的执行。一个进程至少有一个线程,一个进程中可以有多个线程,这样的应用程序称为多线程程序,多线程之间互不影响
线程一般有两种调度方式:
Java中多线程采用抢占式调度。
创建新执行线程有两种方法:
Java 使用 java.lang.Thread 类代表线程,所有的线程对象都必须是 Thread 类或其子类的实例。
Java 中通过继承 Thread 类来创建并启动多线程的步骤如下:
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run:" + i);
}
}
}
/*创建多线程的第一种方式:创建Thread类的子类*/
public class Main {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
}
输出结果是随机抢占的:
main:0
main:1
main:2
run:0
main:3
run:1
main:4
run:2
main:5
main:6
run:3
main:7
run:4
......
这里再理解一下多线程的原理:
当JVM执行main方法时,会开辟一条main方法通向CPU的路径,这个路径叫做main线程,主线程。CPU通过这个线程执行main方法
当我们new了一个Thread类的子类对象并调用run方法后,JVM会再开辟一条通向CPU的新线程(路径),用来执行run方法
对于CPU而言,就有了两条线程,两个线程会一起抢夺CPU的执行权,CPU会随意选择一条路径执行,我们无法控制CPU
上述代码中,多线程的内存图解如下所示:
由图我们可以看出,当创建了一个Thread类的子类对象并调用run方法后,会在内存中开辟一个新的栈空间,用来执行该run方法。
多个线程在不同的栈空间中执行,因此多个线程之间互不影响。
Thread 类的常用方法
public String getName()
:获取当前线程的名称static Thread currentThraed()
:返回对当前正在执行的线程对象的引用public class MyThread extends Thread{
@Override
public void run() {
//方法一:使用Thread类中的getName()方法
String name = getName();
System.out.println(name);
//方法二:使用静态方法currentThread()获取到当前正在执行的线程,使用线程中的方法getName()获取线程名称
Thread th = Thread.currentThread();
System.out.println(th);
}
}
public class ThreadGetName {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start(); //Output:Thread-0
new MyThread().start(); //Output:Thread-1
}
}
输出结果:
Thread-0
Thread-1
Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
void setName(String name)
:改变线程名称,使之与参数 name 相同Thread(String name)
:分配新的 Thread 对象public class MyThread extends Thread{
public MyThread() {
}
//方法二:创建带参构造方法,参数传递线程的名称,调用父类带参构造方法,将线程名称传递给父类
public MyThread(String name) {
super(name);
}
@Override
public void run() {
//获取线程名称
System.out.println(Thread.currentThread().getName());
}
}
public class ThreadSetName {
public static void main(String[] args) {
//方法一:使用Thread中的方法setName()
MyThread mt = new MyThread();
mt.setName("小明");
mt.start();
new MyThread("旺财").start();
}
}
public static void sleep(long millis)
:使当前正在执行的程序以指定的毫秒数暂停(暂时停止执行)。毫秒数结束后线程继续执行public class Main {
public static void main(String[] args) {
//模拟秒表
for (int i=1; i<=60; i++) {
System.out.println(i);
//使用Thread类的sleep()方法让程序睡眠一秒
try {
Thread.sleep(1000); //Thread.sleep()方法本身存在异常,所以需要进行异常处理
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//程序的运行结果为每隔一秒输出一个i
创建多线程程序的第二种方法:实现 Runnable 接口
java.lang.Runnable:
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个 run 的无参构造方法
java.lang.Thread 类中的构造方法:
Thread(Runnable target):分配新的 Thread 对象
Thread(Runnable target, String name):分配新的 Thread 对象
创建多线程步骤:
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
//2.在实现类中重写Runnable接口的run()方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
public class Main {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(run);
//5.调用Thread类中的start()方法,开启新的线程,执行run()方法
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
/*输出结果为:
main--->0
main--->1
Thread-0--->0
Thread-0--->1
Thread-0--->2
main--->2
Thread-0--->3
......
*/
以上两种创建多线程的方式,有什么区别:
采用 Runnable 接口创建多线程的好处:
匿名内部类的作用:简化代码
格式:
new 父类/接口 () {
//重写父类/接口方法
}
public class InnerClassThread {
public static void main(String[] args) {
//Thread方法
new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}.start();
//Runnable方法
//Runnable r = new RunnableImpl(); //多态
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
};
new Thread(r).start();
//简化接口的方法
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}).start();
}
}
当多线程访问同一资源时,且多个线程对资源有写的操作,则容易产生线程安全问题。
例:举例模拟电影票三个窗口,同时售卖1-100号的电影票
/*如电影院中,多个窗口同时卖1-100号的电影票*/
public class RunnableImpl implements Runnable{
//多个线程共享票源
private int ticket = 100;
@Override
public void run() {
while(true) { //重复卖票
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
public class Main {
//模拟卖票,三个线程同时开启,对共享票进行出售
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
在上述案例中,三个线程同时对共享数据 ticket 进行读写操作,最终的输出结果显示,出现了重复票源及非法票源,即出现了线程安全问题:
//上述案例输出结果:
Thread-0正在卖第100张票
Thread-1正在卖第100张票
Thread-2正在卖第100张票
Thread-2正在卖第97张票
Thread-1正在卖第97张票
Thread-0正在卖第96张票
...
Thread-0正在卖第1张票
Thread-2正在卖第0张票
Thread-1正在卖第-1张票
Java中提供了同步机制:synchronized
当某个线程修改共享资源时,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作。保证了数据的同步性,解决线程不安全的问题。
有三种方法完成同步操作:
synchronized 关键字可以用于方法中的某个区块中,表示只对这个区域的资源进行互斥访问。
格式:
synchronized (同步锁对象) {
//需要同步操作的代码
}
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外等着(BLOCKED)
如上述卖票案例,修改 run() 方法如下,即可解决线程安全问题:
/*第一种方法:采用同步代码块*/
public class RunnableImpl implements Runnable{
private int ticket = 100; //多个线程共享票源
//创建一个锁对象
Object obj = new Object();
@Override
public void run() {
while(true) {
synchronized (obj) {
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
}
public class Main {
//模拟卖票,三个线程同时开启,对共享票进行出售
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
原理:
线程t0获取锁对象,进入同步代码块执行
线程t1发现没有锁对象,就会进入阻塞状态,等待t0线程归还锁对象
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁,无法进入同步代码块
上述方法的不足之处是:程序需要频繁的判断锁,获取锁,释放锁,程序的效率会降低
使用 synchronized 修饰的方法为同步方法。保证某线程执行该方法时,其他线程只能在方法外等候。
格式:
public synchronized void method() {
//可能会产生线程安全问题的代码
}
如上述卖票案例,定义一个同步方法,即可解决线程安全问题:
/*第二种方法:采用同步方法*/
public class RunnableImpl implements Runnable{
private int ticket = 100; //多个线程共享票源
@Override
public void run() {
System.out.println("this:" + this); //this:com.example.demo.Demo03Thread.Demo05.RunnableImpl@33c7353a
while(true) {
playTicket();
}
}
//定义一个同步方法
public synchronized void playTicket(){
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
public class Main {
//模拟卖票,三个线程同时开启,对共享票进行出售
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
System.out.println("run:" + run); //run:com.example.demo.Demo03Thread.Demo05.RunnableImpl@33c7353a
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
通过上述代码中的输出测试可以得到验证:
同步方法的锁对象,其实就是实现类对象(即上述的new RunnableImpl()),也就是this。
静态同步方法的锁对象不能是 this,this 是创建对象后产生的,但静态方法优先于对象。
静态同步方法的锁对象是本类的 class 属性,也是 class 文件对象(反射)
public class RunnableImpl implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while(true) {
playTicket();
}
}
public static /*synchronized*/ void playTicket(){
/*这种写法会产生错误。因为 this 是在静态方法之后产生的*/
//synchronized (this) {
synchronized (RunnableImpl.class){
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
java.util.concurrent.locks.Lock 接口,提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作。
Lock 锁也称为同步锁,Lock 中的方法:
void lock()
:获取锁
void unlock()
:释放锁
java.util.concurrent.locks.ReentrantLock implements Lock 接口
Lock 锁的使用步骤:
如下代码示例,建议将 unlock() 放入 finally 中,这样无论程序是否发生异常,都会释放锁对象,提高程序效率。
/*第三种方法:采用Lock锁*/
public class RunnableImpl implements Runnable{
private static int ticket = 100;
//1. 在成员位置创建一个 ReentrantLock 对象
Lock l = new ReentrantLock();
@Override
public void run() {
//2. 在可能会出现安全问题的代码前调用 Lock 接口的 lock() 方法获取锁
l.lock();
while(true) {
if(ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3. 在可能会出现安全问题的代码后调用 Lock 接口的 unlock() 方法释放锁
l.unlock();
}
}
}
}
}
public class Main {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
java.lang.Thread.State 中给出了六种线程状态
线程的状态图如下:
其中,阻塞状态指:具有CPU的执行资格,等待CPU空闲时执行
休眠状态指:放弃CPU的执行权,CPU空闲也不执行
在之前的案例中,我们在 run() 方法中通过 sleep(),强制当前正在运行的线程休眠(暂停执行),以“减慢进程”。
当调用 sleep() 方法时,当前执行的线程就会进入休眠状态,也就是所谓的 Timed Waiting(计时等待)。
注意:
进入Timed Waiting 状态的一种常见情况是调用 sleep() 方法,单独的线程也可以调用
为了让其他线程有机会执行,可以将 Thread.sleep() 的调用放 run() 之内。这样才能保证该线程在执行过程中睡眠
sleep 与锁无关,线程睡眠到期自然苏醒,并返回到 Runnable(可运行)状态
如线程A和线程B使用同一锁,如果线程A获取到锁,线程A进入到 Runnable 状态,那么线程B就会进入到Blocked锁阻塞状态
这是由 Runnable 状态进入Blocked状态,除此之外,Waiting 和 Time Waiting 状态也会在某种情况下进入阻塞状态。
Object 中的方法:
void wait()
:在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
:唤醒在此对象监视器上等待的单个线程。会继续执行 wait() 方法之后的代码
/*等待唤醒案例:线程之间的通信*/
public class WaitAndNotify {
public static void main(String[] args) {
//创建锁对象,保证锁对象唯一
Object obj = new Object();
//创建顾客线程
new Thread(){
@Override
public void run() {
while(true) {
//保证等待和唤醒的线程只能有一个执行,所以需要使用同步技术
synchronized (obj) {
System.out.println("告诉老板需要的包子数量和种类");
try {
obj.wait(); //调用wait()方法,放弃cpu的执行,进入到waiting状态(无限等待)
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,开吃");
System.out.println("====================");
}
}
}
}.start();
//创建老板线程
new Thread(){
@Override
public void run() {
while(true) {
//花了5s做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("老板5s之后做好的包子,并告知顾客");
obj.notify();
}
}
}
}.start();
}
}
//告诉老板需要的包子数量和种类
//老板5s之后做好的包子,并告知顾客
//包子已经做好了,开吃
//====================
//告诉老板需要的包子数量和种类
//老板5s之后做好的包子,并告知顾客
//包子已经做好了,开吃
//====================
//......
wait 有一个带参数的方法 wait(long m),线程进入到 TimeWaiting(计时等待)有两种方法:
唤醒除了 notify() 方法之外,还有方法 notifyAll() 方法,若有多个等待线程,notify() 只能随机唤醒其中的一个线程,notifyAll() 可以唤醒所有等待中的线程
多个线程并发执行时,在默认情况下CPU是随机切换线程的,若需要多个线程共同完成某一任务,并且有规律执行,则需要多线程之间协调通信,以达到多线程共同操作同一份共享数据,避免对同一共享数据的争夺 。
我们需要一定手段使各个线程有效利用资源——等待唤醒机制。
等待唤醒机制常用到的三种方法,即上面学到的三种:
即使只通知了一个等待的线程,被通知的线程也不能立刻恢复执行,因为此刻它已经不持有锁,需要再次尝试获取锁,成功后才能在当初调用 wait 方法之后的地方恢复执行
注意:
下面是一个店家卖包子,顾客吃包子的实例,实现了等待唤醒机制:
//包子类
public class BaoZi {
String pi; //包子皮属性
String xian; //包子馅属性
boolean flag = false; //包子的状态,有包子true和没有包子false
}
//包子铺类。包子铺线程和包子线程之间的通信是互斥关系,采用同步技术,保证两个线程同时只有一个执行。
//锁对象必须唯一。可采用包子对象作为锁对象
public class BaoZiPu extends Thread{
private BaoZi bz; //创建包子对象,作为锁对象
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//设置线程任务,制作包子
@Override
public void run() {
int count = 0;
while(true) {
synchronized (bz) {
if(bz.flag == true) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行,包子铺生产包子
if(count%2 == 0) {
bz.pi = "薄皮";
bz.xian = "三鲜";
} else {
bz.pi = "冰皮";
bz.xian = "牛肉";
}
count++;
System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
try {
Thread.sleep(3000); //生产包子需要三秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
bz.flag = true; //唤醒顾客线程,让顾客吃包子
bz.notify();
System.out.println("包子铺已经生产好了" + bz.pi + bz.xian + "包子,顾客可以开吃了");
}
}
}
}
//顾客类
public class GuKe extends Thread{
private BaoZi bz; //创建包子对象,作为锁对象
public GuKe(BaoZi bz) {
this.bz = bz;
}
//设置线程任务,制作包子
@Override
public void run() {
while(true) {
synchronized (bz) {
if(bz.flag == false) { //没有包子,则等待生产包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行的代码,吃包子
System.out.println("顾客正在吃:" + bz.pi + bz.xian + "的包子");
bz.flag = false;
bz.notify(); //唤醒包子铺线程,继续生产包子
System.out.println("顾客已经把" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
System.out.println("====================");
}
}
}
}
public class Main {
public static void main(String[] args) {
BaoZi bz = new BaoZi(); //创建包子对象
new BaoZiPu(bz).start(); //创建包子铺线程
new GuKe(bz).start(); //创建顾客线程
}
}
若并发的线程数量很多,并且每个线程都是执行一段很短的时间就结束了,这样频繁创建线程会大大降低系统的效率,因为频繁创建线程和销毁线程都需要消耗时间。
Java中可以通过线程池实现线程的复用,其中的线程可以反复使用
若线程池中已无空闲线程,则任务队列中的剩余任务等待执行,等待其他某个任务执行完毕后,归还线程到线程池,再从线程池中获取线程,执行任务
线程池的底层原理其实就是一个容器,一个集合(比如ArrayList,HashSet,LinkedList,HashMap),应该优先选用LinkedList< Thread >
当程序第一次启动时,创建多个线程并保存到一个集合中
当要使用线程时,从集合中取出线程使用 Thread t = linked.removeFirst();
当使用完毕线程后,将线程归还给线程池 linked.addLast(t);
在JDK1.5之后,jdk内置了线程池,可直接使用
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors 类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
:创建一个可重用固定线程数的线程池java.util.concurrent.ExecutorService:线程池接口,用来从线程池中获取线程,调用 start() 方法执行线程任务
其中包含方法:
submit(Runnable task)
:提交一个 Runnable 任务用于执行
void shutdown()
:关闭/销毁线程池
线程池的使用步骤:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
//1.使用线程池的工厂类Executors中提供的静态方法newFixedThreadPool()生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//3.调用ExecutorService中的方法submit(),传递线程任务(实现类),开启线程,执行run方法
//线程池会一直开启,使用完线程后,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl()); //pool-1-thread-1创建了一个新线程执行
es.submit(new RunnableImpl()); //pool-1-thread-2创建了一个新线程执行
es.submit(new RunnableImpl()); //pool-1-thread-1创建了一个新线程执行
//(不建议执行)4.可以调用 ExecutorService 中的方法 shutdown(),销毁线程池
es.shutdown();
es.submit(new RunnableImpl()); //会抛出java.util.concurrent.RejectedExecutionException异常,线程池被销毁,不能获取线程
}
}
//2. 创建一个类,实现 Runnable 接口,重写 run 方法,设置线程任务
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "创建了一个新线程执行");
}
}
函数式编程思想概述
数学中,函数即有输入量,有输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法,强调做什么,而不是以什么形式做
冗余的 Runnable 代码
比如我们在采用 Runnable 的匿名内部类实现多线程时,
//创建Runnable接口实现类
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "新线程创建");
}
}
public class Main {
public static void main(String[] args) {
Runnable r = new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "新线程创建");
}
};
new Thread(r);
new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "新线程创建");
}
}).start();
}
}
我们通过以上方法可以发现:
在Java8(JDK1.8)中,加入了 Lambda 表达式
Lambda 标准格式
Lambda 格式: (参数列表) -> {一些重写方法的代码}
如Runnable 接口中 run 方法的定义:public static void run(),即制定了一种做事情的方案,无参数,无返回值,代码块(方法体)为方案的具体执行步骤。
同样的语义体现在 Lambda 语法中:
() -> System.out.println("新线程创建");
如,通过 Lambda 表达式实现多线程:
/*使用Lambda实现多线程*/
public class LambdaMain {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "新线程创建");
}
).start();
}
}
Lambda 的无参数无返回值
public interface Cook {
public abstract void makeFood();
}
public class Main {
public static void main(String[] args) {
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//使用Lambda表达式
invokeCook(()->{System.out.println("吃饭了");});
//使用Lambda省略格式
//invokeCook(()->System.out.println("吃饭了"));
}
public static void invokeCook(Cook cook) {
cook.makeFood();
}
}
Lambda 的有参数有返回值
import java.util.Arrays;
import java.util.Comparator;
/*使用数组存储多个Person对象,对数组中的对象通过Arrays.sort()对年龄进行排序*/
public class Main {
public static void main(String[] args) {
Person[] arr = {
new Person("小王", 23),
new Person("小李", 14),
new Person("小张", 27),
};
/* Arrays.sort(arr, new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});*/
//使用Lambda表达式
Arrays.sort(arr, (Person o1, Person o2)->{
return o1.getAge() - o2.getAge();
});
//使用Lambda省略格式
//Arrays.sort(arr, (o1, o2)-> o1.getAge() - o2.getAge());
for (Person person : arr) {
System.out.println(person);
}
}
}
public class Person {
private String name;
private int age;
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
invokeCalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a+b;
}
});
//使用Lambda表达式
invokeCalc(10, 20, (int a, int b) -> {
return a+b;
});
}
public static void invokeCalc(int a, int b, Calculator c) {
int sum =c.calc(a, b);
System.out.println(sum);
}
}
public interface Calculator {
public abstract int calc(int a, int b);
}
Lambda 省略格式
Lamda 在标准格式基础上,可以进一步省略,省略写法的规则为:
如上述例子中,Lambda 表达式可以省略为:
invokeCalc(10, 20, (a, b) -> a+b);
Lambda 的使用前提
注意:
有且仅有一个抽象方法的接口,称为“函数式接口”