【Java系列笔记006】Java语言高级(五)第九、十章

第九章:多线程

第一节:线程实现方式

1.1 并发与并行
  • 并发:指两个或多个事件在同一个时间段内发生
  • 并行:指两个或多个事件在同一时刻发生(同时发生)

在这里插入图片描述

1.2 线程与进程
  • **进程:**指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程

  • **线程:**线程是进程的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

1.3 线程概念

**cup:**中央处理器,对数据进行计算,指挥电脑中软件和硬件干活

**cup的分类:**AMD Inter:Inter Core(核心) i7 8846 4核心8线程

**8线程:**同时执行8个任务

点击功能执行,就会开启一条程序到cpu的执行路径,cup就可以通过这个路径执行功能,这个路径有一个名字,叫做线程

线程属于进程,是进程中的一个执行单元,负责程序的执行

多线程的好处:

1、效率高

2、多个线程之间互不影响

在这里插入图片描述

1.4 线程调度
分时调度:

所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

抢占式调度:

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度

1.5 主线程

**主线程:**执行(main)方法的线程

**单线程程序:**java程序中只有一个线程,执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cup就可以通过这个路径来执行main方法,而这个路径有一个名字,叫main(主)线程

1.6 创建线程类

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或者其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流,即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

Java中通过继承Thread类来创建并启动多线程的步骤如下:

1、定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体

2、创建Thread子类的实例,即创建了线程对象

3、调用线程对象的start()方法来启动线程

创建多线程程序的第一种方式:

创建Thread类的子类

实现步骤:

  • 1、创建一个Thread类的子类

  • 2、在Thread类的子类中重写Thread类中的run()方法,设置线程任务(开启线程要做什么)

  • 3、创建Thread类的子类对象

  • 4、调用Thread类中的start()方法,开启新的线程,执行run()方法

    • public void start():使线程开始执行;Java虚拟机调用该线程的run()方法

      结果是两个线程并发地运行,当前线程(main线程)和另一个线程(创建的新进程,执行其run方法)。多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动

    Java程序属于抢占式调度,哪个线程优先级高,哪个线程就优先执行;同一个优先级,随机选择一个执行

创建线程方式二:

采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可

步骤:

1、创建一个Runnable接口的实现类

2、在实现类中重写Runnadle接口的run方法,设置线程任务

3、创建一个Runnadle接口的实现类对象

4、创建Thread类对象,构造方法中传递Runnalde接口的实现类对象

5、调用Thread类中的start方法,开启新的线程执行run方法

public class Thread_Creat_Second implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

main:
Thread_Creat_Second run=new Thread_Creat_Second();
Thread t=new Thread(run);
t.start();
1.7 多线程原理

呈现随机执行的原因:

在这里插入图片描述

多线程内存图解:

当执行start方法时,会开辟一个新的栈空间,并执行此对象的run方法

在这里插入图片描述

1.8 Thread类
构造方法:
  • public Thread():分配一个新的线程对象
  • public Thread(String name):分配一个指定名字的新的线程对象
  • public Thread(Runnable target):分配一个带有指定目标的线程对象
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字
常用方法:
  • public String getName():获取当前线程名称
public class getName_Demo extends Thread{
    @Override
    public void run() {
        System.out.println(getName());
    }
}
  • public String setName():设置线程名称
public class Thread_setName extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
main方法中:
Thread_setName s=new Thread_setName();
s.setName("s1");
  • public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法

  • public void run():此线程要执行的任务在此定义代码

  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)

    • for (int i = 0; i < 60; i++) {
      System.out.println(i);
      	try {
      		Thread.sleep(1000);
      	} catch (InterruptedException e) {
      		e.printStackTrace();
      	}
      }
      
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用

获取线程的名称:

  • 使用Thread类中的方法getName()

  • 可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称

    • static Thread currentThread():返回对当前正在执行的线程对象的引用

    • public class getName_Demo extends Thread{
          @Override
          public void run() {
              System.out.println(Thread.currentThread.getName());
          }
      }
      

设置线程的名称:

  • 使用Thread类中的方法setName(名字)

    • void setName(String name):改变线程名称,使之与参数name相同
  • 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字。

    • Thread(String name):分配新的Thread对象

    • public MyThread(String name){
          super(name);
      }
      
      new MyThread("张三").start();
      
1.9 Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享,但是如果实现Runnable接口的话,很容易实现资源共享

总结:

实现Runnable接口比继承Thread类所具有的优势:

1、适合多个相同的程序代码的线程去共享同一个资源

2、可以避免java中的单继承的局限性

  • 一个类只能继承一个类,类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其他的类,实现其他的接口

3、增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立

  • 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦),实现类中,重写了run方法:用来设置线程任务;创建Thread类对象,调用start方法:用来开启新线程

4、线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类

**扩充:**在Java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实就是在操作系统中启动了一个进程

1.10 匿名内部类方式实现线程的创建

**匿名:**没有名字

**内部类:**写在其他内部的类

**匿名内部类作用:**简化代码

把子类继承父类,重写父类的方法,创建子类对象合成一步完成

把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成

**匿名内部类的最终产物:**子类/实现类对象,而这个类没有名字

格式:

new 父类/接口{
    重写父类/接口中的方法
};
public class Thread_Creat_Annoy {
    //实现父类
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }.start();

        //实现接口
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
    }
}

第二节:线程同步机制

2.1 线程安全即产生安全问题的原理

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其它的变量的值也和预期是一样的,就是线程安全的

多线程访问了共享的数据,会产生线程安全问题

在这里插入图片描述

产生线程安全问题的原理:

在这里插入图片描述

2.2 线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复数据与不存在数据的问题,Java中提供了**同步机制(synchronized)**来解决

同步操作的三种方式:

1、同步代码块

2、同步方法

3、锁机制

2.3 同步代码块
  • 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

格式:

synchronized(同步锁){
    需要同步操作的代码
}

**同步锁:**对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

1、锁对象,可以是任意类型

2、多个线程对象,要使用同一把锁

锁对象的作用:

把同步代码块锁住,只让一个线程在同步代码块中执行

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着

public class Block implements Runnable{
    //定义多个线程共享的票源
    private int ticket=100;
    Object obj=new Object();

    @Override
    public void run() {
        //先判断票是否存在
        while(true)
            synchronized (obj){
                if (ticket>0) {
                    System.out.println(Thread.currentThread().getName() + "------>正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
    }
}
同步技术的原理:

使用了一个锁对象,这个锁叫做同步锁,也叫对象锁,也叫对象监视器

3个线程一起抢夺cpu的使用权,谁抢到了谁执行run方法买卖票

t0抢到了cup的执行权,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行;

t1抢到了cup的执行权,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现没有,就会进入到阻塞状态,会一直等待t0线程归还锁对象,直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能获取到锁对象进入到同步中执行

总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步

同步保证了只能有一个线程在同步中执行共享数据,保证了安全,程序频繁地判断锁,获取锁,程序的效率会降低

在这里插入图片描述

2.4 同步方法
  • **同步方法:**使用synchronized修饰的方法就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着

格式:

修饰符 synchronized 返回值类型 方法名(参数列表){
    可能会产生线程安全问题的代码
}

同步方法的锁对象是谁:就是实现类对象

  • 对于非static方法,同步锁就是this
  • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)

使用步骤:

1、把访问了共享数据的代码抽取出来,放到一个方法中

2、在方法上添加synchronized修饰符

原理:

同步方法也会把方法内部的代码锁住,只让一个线程执行。

public class Common_Method implements Runnable{

    private int ticket=100;

    @Override
    public void run() {
        while (true)
            payTicket();
    }
    //定义一个同步方法
    public synchronized void payTicket(){
        if (ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "------>正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}
2.5 静态同步方法

在同步方法前面添加static关键字即可

**静态同步方法的锁对象:**不能是this,this是创建对象之后产生的,静态方法优先于对象,静态方法的锁对象是本类的class属性---->class文件对象(反射)

public class Static_Method implements Runnable{
    private static int ticket=100;

    @Override
    public void run() {
        while (true)
            payTicket();
    }

    private static synchronized void payTicket(){
        if (ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "------>正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}
2.6 Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock():加同步锁
  • public void unlock():释放同步锁

使用步骤:

1、在成员位置创建一个RenntrantLock对象

java.util.concurrent.locks.ReentrantLockimplements Lock接口

2、在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

3、在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁

public class Lock_Thread implements Runnable{
    private int ticket=100;

    Lock l=new ReentrantLock();

    @Override
    public void run() {
        while (true){
            l.lock();
            if (ticket>0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "------>正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    l.unlock();//无论程序是否异常,都会把锁释放
                }
            }
        }
    }
}

第三节:线程状态

3.1 线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一致处于执行状态。在线程的生命周期中,共有6中线程状态,在API中java.lang.Thread.State这个枚举中给出

线程状态导致状态发生条件
New(新建)线程刚被创建,但是并未启动。还没调用start方法
Runnable(可运行)线程可以在Java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waitting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notifyAll方法才能够唤醒
Timed Waiting(计时等待)同Waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡

在这里插入图片描述

3.2 Timed Waiting(计时等待)

Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。

注意:

1、进入Timed_Waiting状态的一种常见情形是调用sleep方法,单独的线程也可以调用,不一定非要有协作关系

2、为了让其他线程有机会执行,可以Thread.sleep()的调用**放线程run()之内。**这样才能保证该线程执行过程中会睡眠

3、sleep与锁无关,线程睡眠到期自动苏醒,并返回Runnable(可运行)状态

sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行

在这里插入图片描述

3.3 Blocked(锁阻塞)

Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态

在这里插入图片描述

3.4 Waiting(无限等待)

Waiting状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态

等待唤醒案例:线程之间的通信

在这里插入图片描述

wait与notify的用法:
public class Waiting_Sale {
    public static void main(String[] args) {
        Object obj=new Object();
        new Thread(){
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("告知老板包子的种类和数量");

                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //唤醒之后执行的代码
                System.out.println("包子做好了,开吃");
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("老板5s之后做好包子告知顾客");
                    obj.notify();
                }
            }
        }.start();
    }
}

进入到TimeWaiting(计时等待)有两种方式:

1、使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态

2、使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来进入到Runnable/Blocked状态

唤醒的方法:

void notify():如果有多个等待线程,随机唤醒一个

void notifyAll():唤醒所有等待的线程

3.5 线程间通信

**概念:**多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同

为什么要处理线程间的通信?

多个线程并发执行时,在默认情况下cpu是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据

如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效地利用资源,而这种手段就是等待唤醒机制

3.6 等待唤醒机制

概述:

等待唤醒机制是多线程间的一种协作机制。在一个线程进行了规定操作后,就进入等待状态(wait()),等待其他线程执行完他们的指定代码之后,再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有的等待线程

等待机制使用的3种方法:

  • wait:线程不再活动,不再参与调度,进入wait set中,因此不会浪费cpu资源,也不会去竞争锁了,这时的线程状态即是Waiting。他还要等着别的线程执行一个特别的动作,也即是**“通知(noify)”**在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中
  • notify:选取所通知对象的wait set中的一个线程释放
  • notifyAll:释放所通知对象的wait set上的全部线程

注意:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不再持有锁,所以它需要再次尝试获取锁(很可能面临其它线程的竞争),成功后才能在当初调用wait方法之后的地方恢复执行

调用wait和notify方法需要注意的细节:

1、wait方法与notify方法必须要由同一个锁对象调用,因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程

2、wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类型都是继承了Object类的

3、wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用者两个方法

第四节:线程池

4.1 线程池概述
  • 线程池:其实就是一个容纳多个线程的容器(集合),其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源

当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们想要使用线程的时候,就可以从集合中取出来线程使用。

Thread t=list.remove(0);返回的是被移除的元素(线程只能被一个任务使用)

Thread t=linked.removeFirst();

当我们使用完毕线程,需要把线程归还给线程池

list.add(t); linked.addLast(t);

在JDK1.5之后,JDK内置了线程池,我们可以直接使用线程池

合理使用线程池带来的三点好处:

1、降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务

2、提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立刻执行

3、提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗太多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

4.2 线程池的使用

java.util.concurrent.Executors:线程池的工厂类,用来生产线程池

Executors类中的静态方法:

static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池

参数:

int nThreads:创建线程池中包含的线程数量

返回值:

ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)

java.util.concurrent.ExecutorService:线程池接口,用来从线程池中获取线程,调用start方法,执行线程任务。

submit(Runnable task):提交一个Runnable任务用于执行

void shutdown:销毁线程池

使用步骤:

1、使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池

2、创建一个类,实现Runnable接口,重写run方法,设置线程任务

3、调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法

4、调用ExecutorService中的方法shutdown销毁线程池(不建议执行)

ExecutorService es= Executors.newFixedThreadPool(5);
es.submit(new RunnableImpl());

第十章:Lambda表达式

第一节:函数式编程思想概述

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

**面向对象的思想:**做一件事情,找一个能解决这个事情的对象,调用对象的方法完成事情

**函数式编程思想:**只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

第二节:冗余的Runnable代码

原始代码:

RunnableImpl run=new RunnableImpl();
Thread th=new Thread(run);
th.start();

匿名内部类用法:

new Thread(new Runnable() {
@Override
public void run() {
	System.out.println(Thread.currentThread().getName()+"又创建了");
	}
}).start();

代码分析:

对于Runnable的匿名内部类用法,可以分析出几点内容:

  • Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心
  • 为了指定run的方法体,不得不需要Runnable接口的实现类
  • 为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不重写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在

第三节:编程思想转换

做什么。而不是怎么做

我们真的希望创建一个匿名内部类对象吗?不,我们只是伪类做这件事情而不得不创建一个对象,我们真正希望做的事情,是将run方法体内的代码传递给Thread类知晓

传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。如果我们将关注点从“怎么做“回归到”做什么“的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要

在2014年3月Oracle发布的Java8(JDK1.8)中,加入了Lambda表达式的重量级新特性

Lambda表达式创建多线程与匿名内部类创建多线程对比:

public class Lambda_Creat {
    public static void main(String[] args) {
        //使用匿名内部类方式实现多线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

        //使用Lambda表达式实现多线程
        new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        }).start();
    }
}
语义分析:

仔细分析该代码中的语义,Runnable接口只有一个run方法的定义:

  • public abstract void run();

即制定了一种做事情的方案(其实就是一个函数):

  • **无参数:**不需要任何条件即可执行该方案
  • **无返回值:**该方案不产生任何结果
  • **代码块(方法体):**该方案的具体执行步骤

同样的语义体现在Lanbda语法中,要更加简单:

()->{System.out.println("多线程任务执行");}
  • 前面的一对小括号即run方法的参数(无),代表不需要任何条件
  • 中间的一个箭头代表将前面的参数传递给后面代码
  • 后面的输出语句即业务逻辑代码

第四节:Lambda标准格式

Lambda格式由三部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为:

(参数列表)->{一些重写方法的代码}

解释说明格式:

():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔

->:传递的意思,把参数传递给方法体{}

{}:重写接口的抽象方法的方法体

第五节:练习:使用Lambda标准格式(无参数返回)

题目:

给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数、无返回值。如下:

public interface Cook{
    void makeFood();
}

在下面的代码中,请使用Lambda的标准格式调用invokeCook方法,打印输出“吃饭了”字样:

public class Cook_Main {
    public static void main(String[] args) {
        //请再此使用Lambda【标准格式】调用invokeCook方法
        invokeCook(()->{
            System.out.println("吃饭了");
        });
    }

    private static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

第六节:Lambda的参数和返回值

需求:

1、使用数组存储多个Person对象

2、对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序

public class Lambda_Main {
    public static void main(String[] args) {
        Person[] arr={
                new Person("柳岩",18),
                new Person("迪丽热巴",19),
                new Person("古力娜扎",15)
        };
        //使用匿名内部类实现重写方法
        Arrays.sort(arr, new Comparator<Person>() {
            @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();
        });
        for (Person person:arr) {
            System.out.println(person);
        }
    }
}

第七节:练习:使用Lambda标准格式(有参有返回)

题目:

给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值:

public interface Calculator{
    int calc(int a,int b);
}
public class Main {
    public static void main(String[] args) {
        //请在此使用Lambda表达式【标准格式】调用invokeCalc方法计算120+130的结果
        //使用匿名内部类
        invokeCalc(120, 130, new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a+b;
            }
        });
        //使用Lambda表达式
        invokeCalc(120,130,(int a,int b)->{
            return a+b;
        });

    }

    private static void invokeCalc(int a,int b,Calculator calculator){
        int result=calculator.calc(a,b);
        System.out.println("结果是:"+result);
    }
}

第八节:Lambda省略格式

Lambda表达式:是可推导、可省略的

范式根据上下文推导出来的内容,都可以省略

可以省略的内容:

1、(参数列表):括号中的参数列表的数据类型可以省略

2、(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略

3、(一些代码):如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,;分号)

  • 注意:要省略{},return,;必须一起省略

第九节:Lambda使用前提

1、使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法

  • 无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda

2、使用Lambda必须具有上下文推断

  • 也就是方法的参数或者局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为接口的实例

    备注:有且仅有一个抽象方法的接口,称为“函数式接口”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值