JAVA-第三部分-异常、多线程及Lambda表达式

271 阅读9分钟

异常

  • 程序在运行期间出现问题
  • Throwable包括Error(非常严重的问题)和Exception(可以解决的问题),两个类
  • Exception:编译期异常
  • RuntimeException:运行期异常,异常处理掉,程序继续运行
  • Error;无法治愈的毛病,必须修改代码
  • 抛出异常,中断运行
public static void main(String[] args) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = sdf.parse("1999-09-09");
    System.out.println(date);
}
  • try...catch处理异常后,可以正常运行
public static void main(String[] args) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = null;
    try {
        date = sdf.parse("1999-09-09");
    } catch (ParseException e) {
        e.printStackTrace();
    }
    System.out.println(date);
}
  • 运行期异常
int[] arr = {1,2,3};
try {
    // 可能出现异常的代码
    System.out.println(arr[3]);
} catch (Exception e) {
    //打印异常
    System.out.println(e);
}

异常的产生与处理

  • 异常的产生 11841628217958_.pic_hd.jpg

throw,

  • 可以在指定的方法中抛出指定的异常,必须写在方法的内部
  • new的对象必须是Exception或者其子类
  • 必须处理抛出的异常,如果后面创建的是RuntimeException或者子类,可以不处理,直接交给JVM;如果创建的是编译异常,则必须要处理
  • 对参数进行合法性校验,如果传递的参数有问题,要抛出异常
public static int getElement(int[] arr, int index) {
    if (arr == null) {
        //运行期异常,不用处理,默认交给JVM处理
        throw new NullPointerException("传递的数组是空");
    }
    //index合法性校验
    if (index < 0 || index > arr.length - 1) {
        //运行期异常
        throw new ArrayIndexOutOfBoundsException("索引越界");
    }
    int i = arr[index];
    return i;
}

Objects非空判断

//调用
method(null);

public static void method(Object o) {
    //合法性判断
    //if (o == null) {
        //throw new NullPointerException("传递的对象是空");
    //}
    //Objects.requireNonNull(o);
    Objects.requireNonNull(o,"传递的对象是空");
}

throws

  • 声明异常
  • 当方法内部抛出异常的时候,就必须处理,通过throws处理异常对象
  • 必须写在方法声明处
  • 后面声明的异常必须是Exception或者其子类
  • 如果抛出多个异常,后面也必须声明多个异常,如果异常对象有子父类关系,直接声明父类异常即可
public static void main(String[] args) throws IOException {
    readFile("/User/apple/test");
}
public static void readFile(String path) throws IOException {
    if (!path.equals("/User/apple/test.doc")) {
        //编译异常,必须处理
        throw new FileNotFoundException("文件路径错误");
    }
    if (!path.endsWith(".doc")) {
        //也可以直接声明Exception,是所有异常的父类
        //IOException 是 FileNotFoundException 的父类,所以可以不写
        throw new IOException("文件后缀名不同");
    }
    System.out.println("读取文件");
}

try..catch处理异常

  • try中可以抛出多个异常对象,可以用多个catch处理这些异常对象
  • 如果try中产生异常,执行catch中的异常处理逻辑,继续执行;如果没有产生异常,则不执行catch中的代码
public static void main(String[] args) throws IOException {
    try {
        readFile("/User/apple/test");
    } catch (Exception e) {
        System.out.println(e);
    }
    System.out.println("后续代码");
}
public static void readFile(String path) throws IOException {
    if (!path.equals("/User/apple/test")) {
        //编译异常,必须处理
        throw new FileNotFoundException("文件路径错误");
    }
    if (!path.endsWith(".doc")) {
        //也可以直接声明Exception,是所有异常的父类
        //IOException 是 FileNotFoundException 的父类,所以可以不写
        throw new IOException("文件后缀名不同");
    }
    System.out.println("读取文件");
}

Throwable

  • 定义了三个处理异常的方法
//打印 文件后缀名不同
System.out.println(e.getMessage());
//打印 java.io.IOException: 文件后缀名不同
System.out.println(e.toString());
//打印 java.io.IOException: 文件后缀名不同
	at com.mzx.java.DemmThrowable.Demo3Main.readFile(Demo3Main.java:26)
	at com.mzx.java.DemmThrowable.Demo3Main.main(Demo3Main.java:9)
e.printStackTrace();

finally代码块

  • 无论是否出现异常都能执行
  • 不能单独使用,必须和try一起使用
  • 不要在finally中写return
  • 一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
try {
    readFile("/User/apple/test");
} catch (Exception e) {
    e.printStackTrace();
} finally {
    System.out.println("资源释放");
}

多个异常

  • 多个异常分别处理,几个异常有几个try...catch
  • 多个异常一次捕获,多次处理,多个catch中的异常如果有子父类关系,子类必须写在上面;如果try中出现异常,会把异常对象抛给catch处理,会从上到下依次赋值给catch处理,如果父类在上面,下面的子类就是废话
try {
    int[] arr = {1,2,3};
    //System.out.println(arr[3]);
    List<Integer> integers = List.of(1, 2, 3);
    System.out.println(integers.get(3));
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println(e);
} catch (IndexOutOfBoundsException e) {
    System.out.println(e);
}
System.out.println("后续代码");
  • 多个异常一次捕获,一次处理;用父类的异常直接处理
try {
    int[] arr = {1,2,3};
    //System.out.println(arr[3]);
    List<Integer> integers = List.of(1, 2, 3);
    System.out.println(integers.get(3));
} catch (IndexOutOfBoundsException e) {
    System.out.println(e);
}
System.out.println("后续代码");
  • 运行时的异常被抛出可以不处理,既不捕获也不声明抛出,默认给虚拟机JVM处理,终止程序

注意事项

  • 父类异常是什么样,子类异常就是什么样
  • 子类重写父类方法,如果父类方法抛出异常,子类抛出异常/子类异常/不抛出异常
  • 如果父类方法不抛出异常,子类重写方法产生异常,不能抛出,只能try...catch处理

自定义异常

  • 继承Exception或者RuntimeException
public class RegisterException extends Exception{
    public RegisterException() {
        super();
    }
    public RegisterException(String message) {
        super(message);
    }
}

多线程

  • java属于抢占式调度,哪个线程的优先级高,就优先哪个线程,同一个优先级,随机选择执行
  • 并发,指两个或多个事件在同一个时间段发生,交替执行;并行,两个或多个在同一时刻发生,同时执行
  • 主线程,执行主方法的路径。JVM执行main方法,main方法会进入栈内存,JVM找操作系统开辟一条main方法通向CPU的执行路径,cpu通过这个路径执行main方法

Thread

  • 多次启动一个线程是非法的,特别是当线程已经结束之行后,不能再重新启动
  • 创建Thread,重写run方法
//调用
public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();

    for (int i = 0; i < 20; i++) {
        System.out.println("main - " + i);
    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("MyThread - " + i);
        }

    }
}
  • 随机执行的原理,当有了一个新线程后,cpu就有了一条新的执行路径,在主线程与新线程之间随机选择执行
  • 内存原理 11851628236896_.pic_hd.jpg

常用方法

  • 获取线程名
System.out.println(new MyThread().getName());
System.out.println(Thread.currentThread().getName());
  • 设置线程名
//直接设置
MyThread mt = new MyThread();
mt.setName("xiaoming");
//带参构造
public MyThread(String name) {
    super(name);
}
  • 暂停,延迟执行
for (int i = 0; i < 60; i++) {
    System.out.println(i);
    Thread.sleep(1000);
}

Runnable

  • 由打算通过某一线程执行其实例的类来实现,必须定义run方法
//实现类
public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }
}
//调用
public static void main(String[] args) {
    RunnableImpl run = new RunnableImpl();
    Thread t = new Thread(run);
    t.start();
    for (int i = 0; i < 20; i++) {
        System.out.println(Thread.currentThread().getName() + " - " + i);
    }
}

与Thread的区别

  • Runnable避免了单继承的局限性,实现了Runnable接口还能继承其他的类
  • 增强了程序的扩展性,把设置线程任务和开启新线程进行分离,用Runnable接口决定任务,用Threadstart方法,开启新线程

匿名内部类创建

  • 简化代码,把子类继承父类,重写父类的方法,创建子类的对象合一步完成;把实现类接口,重写接口中的方法,创建实现类对象合成一步完成
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
},"lisi").start();

new Thread("zhangsan") {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}.start();

线程安全

  • 原理,多个线程同时操作某一个变量或者一段代码,当一个线程在操作变量,而同时又被另一个线程操作,导致变量结果异常 11881628242961_.pic_hd.jpg
  • 让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权,让其他线程保持等待,执行完后,让其他线程访问

同步代码块

  • 放需要同步的代码块
  • 同步代码块的锁对象,可以使用任意的对象,但是必须保证多个线程使用的锁对象是同一个
  • 锁对象的作用,把同步代码块锁住,只让一个线程在同步代码块中执行
//锁对象
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 --;
            } else break;
        }
    }
}
  • 原理,当一个线程检测synchronized代码块,发现有锁对象,进入执行,另一个线程访问时,发现没有锁对象,等待;等待第一个线程执行完代码,归还锁对象后,才能进入;同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
  • 同步,保证了只能有一个线程在同步中执行共享数据,但是导致程序频繁地判断锁,降低效率

同步方法

  • 同步方法的锁对象就是实现类对象,this
public  synchronized void payTicket() {
    if (ticket > 0) {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 正在卖出第 " + ticket + " 张票");
        ticket --;
    };
}
  • 静态同步方法,锁对象是本类的class属性,class文件对象的反射
public static synchronized void payTicketStatic() {
    if (ticket > 0) {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 正在卖出第 " + ticket + " 张票");
        ticket --;
    };
}

// 锁对象 RunnableImpl.class
synchronized (RunnableImpl.class) {
    if (ticket > 0) {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 正在卖出第 " + ticket + " 张票");
        ticket --;
    }
}

Lock

  • 普通写法
Lock l = new ReentrantLock();
@Override
public void run() {
    while (true) {
        //在可能出现安全的问题代码前调用
        l.lock();
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 正在卖出第 " + ticket + " 张票");
            ticket --;
        } else {
            break;
        }
        //在处理完后,打开锁
        l.unlock();
    }
}
  • 更好的写法,配合try...catch...finally,即使释放锁
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();
            }
        } else {
            l.unlock();
            break;
        }
    }
}

线程状态

11901628476518_.pic_hd.jpg

  • 无限等待状态,一个正在无限等待另一个线程执行一个特别的(唤醒)动作的线程,线程之间的通信
  • 消费者/生产者模型
//创建锁对象
Object obj = new Object();
//消费者
new Thread(new Runnable() {
    @Override
    public void run() {
        //一直买包子
        while (true) {
            synchronized (obj) {
                System.out.println("告知老板要的包子种类");
                try {
                    //阻塞等待
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //被唤醒之后的行为
                System.out.println("收到包子");
                System.out.println("------------------");
            }
        }
    }
}).start();

//生产者
new Thread(new Runnable() {
    @Override
    public void run() {
        //一直做包子
        while (true) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj) {
                System.out.println("老师在5s之后做好了");
                //唤醒
                obj.notify();
            }
        }
    }
}).start();
  • 计时等待,线程睡醒,进入Runnable/Blocked状态
obj.wait(50);
Thread.sleep(5000);
  • 唤醒
obj.notify();
obj.notifyAll();

等待唤醒机制

  • 多个线程并发执行,需要线程操作一份数据,有规律地执行,需要线程之间的通信

wait和nofiy

  • wait等待,notify唤醒,唤醒等待时间最长的
  • 必须在同步代码块,或者同步方法中使用
  • 锁对象可以是任意对象,任意对象都可以调用,必须要由同一个锁对象调用

线程池

  • 管理线程
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//调用线程池的线程执行方法
//线程池会一直开启,使用完之后,会自动把线程归还给线程池
threadPool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});
threadPool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});
threadPool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});
//销毁线程池,但是不建议使用
threadPool.shutdown();

Lambda表达式

  • 函数式思想,强调做什么
  • Runnable为例,类似闭包
//匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}).start();
//lamdba表达式
new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
}).start();

格式

  • 无参数,无返回值调用实现接口
public static void invokeCook(Cook cook) {
    cook.makeFood();
}

//调用
invokeCook(new Cook() {
    @Override
    public void makeFood() {
        System.out.println("吃饭了");
    }
});
invokeCook(() -> {
    System.out.println("吃饭了");
});
  • 有参数,有返回值
Person[] arr = {
        new Person("xiaoming", 18),
        new Person("zhangsan", 17),
        new Person("lisi", 20),
        new Person("wangwu", 14),
};
//降序
Arrays.sort(arr, new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
        return o2.getAge() - o1.getAge();
    }
});
for (Person person : arr) {
    System.out.println(person);
}
System.out.println("------------");
//升序
Arrays.sort(arr, (Person o1, Person o2) -> {
    return o1.getAge() - o2.getAge();
});
for (Person person : arr) {
    System.out.println(person);
}
  • 自定义接口实现
public static void invokeCalculator (int a, int b, Calculator calculator) {
    System.out.println(calculator.add(a,b));
}
//调用
invokeCalculator(3, 4, new Calculator() {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
});
invokeCalculator(3,4,(int a, int b) -> {
    return a + b;
});

优化

  • 凡是可以根据上下文推导出来的内容,都可以省略不写
  • 参数列表,括号中的参数列表的数据类型,可以省略不写;括号中的参数如果是有一个,那么类型和()都可以省略
  • 方法体,如果{}中的代码只有一行,无论是否有返回值,都可以省略({}、return、分号),要一起省略
  • 无参数
new Thread( () -> System.out.println(Thread.currentThread().getName())).start();
  • 有两个参数,有一句return
Arrays.sort(arr, (o1, o2) -> o1.getAge() - o2.getAge());
  • 有一个参数,一个return
public static String invokeStudent(String name, Student student) {
    return student.name(name);
}
//调用
System.out.println(invokeStudent("zhangsan", name -> name));

前提

  • 必须要有接口,而且接口中只有一个抽象方法
  • 必须要有上下文推断
  • 函数式接口,有且只有一个抽象方法的接口