异常与多线程

第一章 异常

1.1 异常的概念

指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。

1.2 异常体系

异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Errorjava.lang.Exception,平常所说的异常是指后者。

Throwable体系:

  • Error:严重错误,无法通处理的错误,只能实现避免。
  • Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理好的。

Throwable常用方法:

  • public void printStackTrace():打印异常的详细信息。

    包含了异常的类型、异常的原因、还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace

  • public string getMessage():获取发生异常的原因。

    提示给用户的时候,就提示错误原因

  • public String toString():获取异常的类型和异常描述信息(不用)。

出现异常,可以把异常的简单类名拷贝到API中查询

扫描二维码关注公众号,回复: 8306567 查看本文章

1.3 异常产生的过程解析

1567066501414

第二章 异常的处理

2.1 抛出异常throw

在Java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。

使用流程:

  1. 创建一个异常对象,封装一些提示信息(信息可以自己编写);
  2. 需要将这个异常对象告知给调用者,通过关键字throw就可以完成,throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行;

使用格式:

throw new 异常类名(参数);

举例:

public class throw关键字 {
    public static void main(String[] args) {
        int[] arr = null;
        /* 传入为null的数组 */
        int element = getElement(arr, 1);
        System.out.println(element);
    }
    public static int getElement(int[] arr,int index){
        if (arr == null){
            /* 手动抛出异常 */
            throw new NullPointerException("这是数组是null");
        }
        return arr[index];
    }
}

2.2 Objects非空判断

类Objects,是由一些静态实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),那么在它的源码中,对对象为null的值进行了抛出异常操作。

  • public static<T> T requireNonNull(T obj):查看指定引用对象不是null;

上述方法源码为:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

使用的好处:在判断是否为空的时候,只需要调用该方法即可判断是否为空指针,减少自行判断的代码量

举例:

import java.util.Objects;

public class throw关键字 {
    public static void main(String[] args) {
        int[] arr = null;
        int element = getElement(arr, 1);
        System.out.println(element);
    }
    public static int getElement(int[] arr,int index){
//        if (arr == null){
//            throw new NullPointerException("这是数组是null");
//        }
        /* 将上述抛出异常换成下面的方法,效果相同,使用更简洁 */
        Objects.requireNonNull(arr,"数组为null");
        return arr[index];
    }
}

2.3 声明异常throws

将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理异常,那么必须通过throws进行声明,让调用者去处理。

关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。

声明异常格式:

修饰符 返回值类型 方法名(参数) throws AAAExcetion,BBBExcetion...{
    throw new AAAExcetion("产生原因");
    throw new BBBExcetion("产生原因");
}

注意事项:

  • throws关键字必须写在方法声明处;
  • throws关键字后边声明的异常必须是Exception或者是Exception的子类;
  • 方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常。如果抛出的多个异常对象有父子类关系,那么直接声明父类异常即可;
  • 调用了一个声明抛出异常的方法,我们就必须处理声明的异常,要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM,要么try...catch自己处理异常

举例:

/* 自定义异常 */
class myException extends Exception{
    public myException(){}
    public myException(String s){
        super(s);
    }
}
public class 自定义异常类 {
    /* 定义异常的函数 */
    public static String func(int score) throws myException {
        System.out.println("1");
        if (score >= 0 && score <= 100) {
            return "ok";
        } else {
            throw new myException("错误输入");
        }
    }
    public static void main(String[] args) {
        /* 捕获异常 */
        try{
            String str1 = func(88);
            System.out.println(str1);
            String str2 = func(120);
            System.out.println(str2);

        } catch (myException e){
            System.out.println("异常信息为:"+e.getMessage());
        } finally {
            System.out.println("结尾代码,都会出现这句话");
        }
        System.out.println("不管上面代码出错否,都不会中断程序,都会出现这句话");
    }
}

2.4 捕获异常try...catch

如果异常出现的话,会立刻终止程序,所以我们得处理异常:

  1. 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws);
  2. 在方法中使用try-catch的语句块来处理异常

try-catch的方式就是捕获异常

  • 捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理;

捕获异常语法如下:

try {
    编写可能出现异常的代码
} catch (异常类型 e) {
    处理异常的代码
    // 记录日志/打印异常信息/继续抛出异常
} 

如何获取异常信息:

Throwable类中定义了一些查看方法:

  • public String getMessage():获取异常的描述信息、原因(简短信息)

  • public String toString():获取异常的类型和异常描述信息(详细信息)

  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台(JVM打印异常对象,默认使用此方法,打印的异常信息是最全面的)

    包含了异常的类型、异常的原因、异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

2.5 异常注意事项

  • 运行时异常被抛出可以不处理,即不捕获也不声明抛出。
  • 如果finally有return语句,永远返回finally中的结果,避免该情况。
  • 如果父类方法抛出多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
  • 如果父类方法没有抛出异常,子类重写父类该方法时也不可以抛出异常,此时子类产生该异常,只能捕获异常,不能声明抛出。

  • 在try...catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。

  • 在异常分类中,除了捕获异常之外的都是未捕获异常,未捕获异常包括Error类以及它的直接子类、间接子类和RuntimeException类以及它的直接子类和间接子类。

    使用捕获异常时,try中不能放置一个不可能出现捕获异常的语句,否则会报错

举例一:

class Fu {
    public void method1() throws Exception{}
    public void method2() throws Exception{}
    public void method3() throws Exception{}
    public void method4() {}
}
class Zi extends Fu {
    /* 父类抛出异常的方法重写,子类抛出与父类相同的异常 */
    @Override
    public void method1() throws Exception{}
    /* 父类抛出异常的方法重写,子类抛出父类异常的子类 */
    @Override
    public void method2() throws IndexOutOfBoundsException{}
    /* 父类抛出异常的方法重写,子类不抛出异常 */
    @Override
    public void method3() {}
    /* 如果父类没有抛出异常,那么子类也不能抛出异常,若是存在异常,自行捕获 */
    @Override
    public void method4() {
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class 子类重写父类抛出异常函数 {
}

举例二:

import java.io.IOException;

public class 不可能出现异常的代码 {
    public static void main(String[] args) {
        try{
            System.out.println("111");
        } catch (IOException E){// 会报错
            System.out.println("捕获异常");
        } catch (IndexOutOfBoundsException e){
            System.out.println("未捕获异常");
        }
    }
}

第三章 自定义异常

在开发中总是有些异常情况是没有定义好的,所以我们需要根据自身业务的异常情况来定义异常类。

异常类如何定义:

  1. 自定义一个编译器异常,继承于java.lang.Exception

    如果方法内部抛出了编译器异常,就必须处理这个异常,要么throws,要么try...catch

  2. 定义一个运行时期的异常类,继承于java.lang.RuntimeException

    运行期异常,无需处理,交给虚拟机处理(中断处理)

/* 自定义一个异常类 */
class myException extends Exception{
    public myException(){}
    public myException(String s){
        super(s);
    }
}
public class 自定义异常类 {
    /* 定义抛出异常的方法 */
    public static String func(int score) throws myException {
        System.out.println("1");
        if (score >= 0 && score <= 100) {
            return "ok";
        } else {
            throw new myException("错误输入");
        }
    }
    public static void main(String[] args) {
        try{
            String str1 = func(88);
            System.out.println(str1);
            String str2 = func(120);
            System.out.println(str2);

        } catch (myException e){
            System.out.println("异常信息为:"+e.getMessage());
        } finally {
            System.out.println("不管上面代码出错否,都会出现这句话");
        }
    }
}

第四章 多线程

4.1 多线程原理

使用多线程的方式一:继承Thread类,并将所执行的代码放置run函数中,最后用start调用

package day13;
class myThread extends Thread {
    private String testName;

    public myThread() {
    }

    public myThread(String testName) {
        this.testName = testName;
    }

    public String getTestName() {
        return testName;
    }

    public void setTestName(String testName) {
        this.testName = testName;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(testName+"-->"+i);
        }
    }
}
public class 多线程 {
    public static void main(String[] args) {
        myThread myThread = new myThread("吖啊");
        /* 以下语句执行后会开启一个不同于main线程的新线程 */
        myThread.start();// 调用start方法,
        for (int i = 0; i < 10; i++) {
            System.out.println("main-->"+i);
        }
    }
}

输出结果的顺序不唯一

1567695362576

4.2 Thread类

java.lang.Thread类中,常用方法如下

构造方法:

  • public Thread():分配一个新的线程对象
  • public Thread(String name):分配一个指定名字的新线程对象
  • public Thread(Runnable target):分配一个带有指定目标的新线程对象
  • public Thread(Runnable target,String name):分配一个带有指定目标和名字的新线程对象

常用方法:

  • public String getName():获取当前线程的名称

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

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

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

    这里可能会有异常,需要进行异常处理,要么try...catch,要么throws

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

获取线程名称(两种方式):

class MyThread extends Thread {
    @Override
    public void run() {
        /* 获取进程名方法一 */
        String name_1 = this.getName();
        System.out.println(name_1);
        /* 获取进程名方法二 */
        Thread thread = Thread.currentThread();
        System.out.println(thread);
        String name_2 = thread.getName();
        System.out.println(name_2);
    }
}
public class 获取线程名称 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        System.out.println(Thread.currentThread().getName());
    }
}

设置线程名称(两种方式):

  1. 使用Thread类中的方法setName;

    void setName(String name):改变线程名称,使之与参数name相同

  2. 创建一个带参数的构造函数方法,参数传递线程的名称;调用父类的带参构造函数,把线程名称传递给父类,让父类(Thread)给子线程设置名字;

class MyThread extends Thread {
    public MyThread() {
    }
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class 设置线程名称 {
    public static void main(String[] args) {
        MyThread myThread_1 = new MyThread();
        /* 方法一 */
        myThread_1.setName("使用setName方法定义的进程名称");
        myThread_1.start();
        /* 方法二 */
        MyThread myThread_2 = new MyThread("使用构造函数定义的进程名称");
        myThread_2.start();
    }
}
// 输出
// 使用setName方法定义的进程名称
// 使用构造函数定义的进程名称

4.3 创建多线程的方式二

实现步骤:

  1. 创建一个Runnable接口的实现类
  2. 在实现类中重写Runnable接口中的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程执行run方法
/* 实现步骤1 */
class RunnableImpl implements Runnable {
    /* 实现步骤2 */
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class 多进程创建的方式二 {
    public static void main(String[] args) {
        /* 实现步骤3 */
        RunnableImpl runnable = new RunnableImpl();
        /* 实现步骤4-5 */
        new Thread(runnable).start();
    }
}

4.4 Thread和Runable的区别

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

总而言是,实现Runnable接口比继承Thread类所具有的优势有:

  1. 适合多个相同的程序代码的线程去共享同一个资源;
  2. 可以避免Java中的单继承的局限性;
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立;
  4. 线程池只能放如实现Runnable或Callable类线程,不能直接放入继承Thread的类;

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

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

public class 匿名类实现多线程 {
    public static void main(String[] args) {
        /* 继承Thread的匿名函数实现多线程 */
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        thread.start();
        /* 接口Runnable使用多态实现多线程 */
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        new Thread(runnable).start();
    }
}

第五章 线程安全问题

5.1 同步代码块

synchronized关键字可以用在方法中的某个区域中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(obj){
    // 需要同步操作的代码区域
}

同步锁(obj):

其中,obj 称为同步监视器,也就是锁,原理是:当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

其中的锁,在非静态方法中可为this,在静态方法中为当前类本身(例如单例模式的懒汉式:Singleton.class)。

  1. 锁对象可以是任意数据类型;
  2. 多个线程对象,需要使用同一把锁;

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块(同时锁也被带进来了),其他的线程发现没有锁了只能等待(BLOCKED)。

几个进程依次获取cpu资源,直到碰到了同步代码块,此时看谁抢到了cpu谁就携带锁,而其他进程则进入阻塞状态,执行完同步代码块后,该进程依然拥有cpu资源,直到循环到下一个同步代码块(或者代码执行完)则会释放cpu资源,然后各阻塞进程唤醒后再次争夺cpu资源,完成下一轮同步代码块。

举例:

class sailTicket implements Runnable {
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"one");
        while (true) {
            System.out.println(Thread.currentThread().getName()+"two");
            /* 同步代码区,其中obj也可以换成this或者sailTicket.class */
            synchronized(obj){
                System.out.println(Thread.currentThread().getName()+"three");
                if (ticket > 0) {
                    System.out.println("正在售卖第" + ticket + "张票" + Thread.currentThread().getName());
                    ticket--;
                }
            }
        }
    }
}

public class 卖票的同步出售 {
    public static void main(String[] args) {
        sailTicket sailTicket = new sailTicket();
        new Thread(sailTicket).start();
        new Thread(sailTicket).start();
        new Thread(sailTicket).start();
    }
}
/* 部分输出结果如下:
Thread-2one
Thread-1one
Thread-0one
Thread-2two
Thread-1two
Thread-0two
Thread-2three
正在售卖第100张票Thread-2
Thread-2two
Thread-0three
正在售卖第99张票Thread-0
Thread-0two
Thread-1three
正在售卖第98张票Thread-1
Thread-1two
Thread-0three
正在售卖第97张票Thread-0
Thread-0two
Thread-2three
*/

5.2 同步方法

使用synchronized修饰的方法,就叫同步方法,保证一个线程在执行该线程的时候,其他线程只能在方法外等着

格式:

public synchronized void method(){
    // 可能会产生线程安全问题的代码
}

同步锁是谁?

​ 对于非static方法,同步锁就是this,

​ 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)作为同步锁

举例:

class sailTicket implements Runnable {
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"one");
        while (true) {
            System.out.println(Thread.currentThread().getName()+"two");
            /* 执行同步方法 */
            method();
        }
    }
    public synchronized void method(){
        System.out.println(Thread.currentThread().getName()+"three");
        if (ticket > 0) {
            System.out.println("正在售卖第" + ticket + "张票" + Thread.currentThread().getName());
            ticket--;
        }
    }
}

public class 卖票的同步出售 {
    public static void main(String[] args) {
        sailTicket sailTicket = new sailTicket();
        new Thread(sailTicket).start();
        new Thread(sailTicket).start();
        new Thread(sailTicket).start();
    }
}
/* 部分输出结果如下:
Thread-1one
Thread-0one
Thread-2one
Thread-1two
Thread-0two
Thread-2two
Thread-1three
正在售卖第100张票Thread-1
Thread-1two
Thread-2three
正在售卖第99张票Thread-2
Thread-2two
Thread-0three
正在售卖第98张票Thread-0
Thread-0two
*/

5.3 Lock锁

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

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

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

举例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class sailTicket implements Runnable {
    private static int ticket = 100;
    public Lock lock = new ReentrantLock();
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"one");
        while (true) {
            System.out.println(Thread.currentThread().getName()+"two");
            /* 加同步锁 */
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"three");
                if (ticket > 0) {
                    System.out.println("正在售卖第" + ticket + "张票" + Thread.currentThread().getName());
                    ticket--;
                }
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                /* 不管是否有错误,都会释放同步锁 */
                lock.unlock();
            }
        }
    }
    public static synchronized void method(){

    }
}

public class 卖票的同步出售 {
    public static void main(String[] args) {
        sailTicket sailTicket = new sailTicket();
        new Thread(sailTicket).start();
        new Thread(sailTicket).start();
        new Thread(sailTicket).start();
    }
}

第六章 线程池

线程池:JDK1.5之后提供的,解决了线程频繁创建、销毁所带来的时间、空间浪费

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

Executor类中的静态方法:

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

参数:

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

返回值:

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

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

常用方法:

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

void shutdown():关闭/销毁线程池

线程池的使用步骤:

  1. 使用线程池的工厂类Executor里面的静态方法newFixedThreadPool生产一个指定线程数量的线程池;
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务;
  3. 调用ExecutorService中的submit方法,传递线程任务(Runnable实现类),开启线程,执行run方法;
  4. 调用ExecutorService中的shutdown方法销毁线程池;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/* 创建Runnable的实现类,并重写run方法 */
class RunnableImpl implements Runnable {
    private int num = 20;
    @Override
    public void run() {
        while (num > 0){
            System.out.println("this is:" + num + Thread.currentThread().getName());
            num--;
        }
    }
}
public class 使用线程池 {
    public static void main(String[] args) {
        /* 生成线程数为3的线程池 */
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        /* Runnable实现类的对象实例 */
        RunnableImpl runnable = new RunnableImpl();
        /* 将runnable作为参数传入线程池的submit方法中,从而获取一个线程执行run任务
         * 若是任务完成或者分配的时间片使用完毕,则自动将线程归还给线程池
         */
        executorService.submit(runnable);
        executorService.submit(runnable);
        executorService.submit(runnable);
    }
}

第七章 Lambda表达式

简化了匿名内部类的书写方式

Lambda使用前提:

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

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

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

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

Lambda表达式的标准格式,由三部分组成:

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

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

  2. 一个箭头:传递的意思,把参数传递给方法体 { };
  3. 一段代码:重写接口的抽象方法;

如果 { }代码中只有一行,无论是否有返回值,都可以省略({},return,分号),其中三个必须同时省略

格式:

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

举例一:

public class 使用Lambda表达式 {
    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() + "Lambda表达式执行了");
        }).start();
        /* 优化Lambda书写方式 */
        new Thread(()-> System.out.println(Thread.currentThread().getName() + "优化版Lambda表达式执行了")).start();
    }
}
/*  输出结果如下:
    Thread-2优化版Lambda表达式执行了
    Thread-0这个匿名内部类执行了
    Thread-1Lambda表达式执行了
*/

举例二:

import java.util.Arrays;
import java.util.Comparator;

class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class 使用Lambda进行排序 {
    public static void main(String[] args) {
        Person[] personArr = {
                new Person("张三",19),
                new Person("李四",17),
                new Person("王五",22)
        };
        /* 方式一:使用匿名内部类排序 */
        Arrays.sort(personArr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        for (Person item : personArr) {
            System.out.println(item);
        }
        /* 方式二:使用Lambda表达式排序,其中参数列表的数据类型Person可以省略 */
        Arrays.sort(personArr, (Person o1, Person o2) -> {
                return o1.getAge() - o2.getAge();
            }
        );
        System.out.println("===========================");
        for (Person item : personArr) {
            System.out.println(item);
        }
    }
}
/*  输出结果如下:
    Person{name='张三', age=19}
    Person{name='李四', age=17}
    Person{name='王五', age=22}
    ===========================
    Person{name='李四', age=17}
    Person{name='张三', age=19}
    Person{name='王五', age=22}
*/

猜你喜欢

转载自www.cnblogs.com/carle/p/12095376.html