1 可变参数

当方法的参数列表数据类型已经确定,但是参数的个数不确定时,可以使用可变参数

  • 格式为修饰符 返回值类型 方法名(数据类型...变量名){}
  • 原理:底层为一个数组,根据传递参数个数不同,会创建不同长度的数组来存储参数,传入参数可以是0个
  • 终极写法 public static void func(Object...obj) ```java package Pro;

public class VarArgs { public static void main(String[] args) { System.out.println(sum()); System.out.println(sum(1)); System.out.println(sum(1,2)); System.out.println(sum(1,2,3)); } public static int sum(int…args){ int sum = 0; for (int n : args) { sum += n; } return sum; } }

  1. <a name="gcFr3"></a>
  2. # 2 异常
  3. - **异常** :程序在执行过程中,出现的非正常情况,最终会导致JVM的非正常停止
  4. <a name="cPvMl"></a>
  5. ## 2.1 异常的分类
  6. - `java.lang.Throwable` 是所有异常的超类,有两个直接子类 `java.lang.Error` 和 `java.lang.Exception`
  7. - **Error** 非常严重的错误,无法通过处理的错误,只能事先避免
  8. - **Exception** 编译期异常,由于使用不当,可以避免的错误
  9. - **RuntimeError** 运行期异常,java程序运行过程中出现的问题
  10. <a name="jmlmN"></a>
  11. ## 2.2 throw/throws
  12. <a name="nEPiW"></a>
  13. ### throw
  14. - 可以使用 `throw` 关键字在指定的方法中抛出指定的异常
  15. - 注意事项
  16. - `throw` 必须写在方法的内部
  17. - `throw` 后 `new` 的对象必须是 `Exception` 或其子类对象
  18. - `throw` 抛出指定的异常对象,就必须处理这个异常对象
  19. - `throw` 后面创建的是 `RuntimeException` 或其子类,可以不处理,默认交给JVM处理
  20. - `throw` 后面创建的是编译异常,就必须处理此异常,要么 `throws` 要么 `try...catch`
  21. ```java
  22. package Pro.Exception;
  23. public class DemoThrow {
  24. public static void main(String[] args) {
  25. int[] arr1 = null;
  26. int[] arr2 = {};
  27. // int e1 = getElement(arr1, 0);
  28. // System.out.println(e1);
  29. int e2 = getElement(arr2, 0);
  30. System.out.println(e2);
  31. }
  32. public static int getElement(int[] arr, int index){
  33. if(arr==null) {
  34. throw new NullPointerException("所传递的数组为空!");
  35. }
  36. if(index<0 || index>=arr.length){
  37. throw new ArrayIndexOutOfBoundsException("数组索引越界!");
  38. }
  39. return arr[index];
  40. }
  41. }

throws

  • 可以使用 throws 关键字将异常对象抛出给方法的调用者处理,最终交给JVM处理
  • 注意
    • throws 必须写在方法声明处
    • throws 声明的异常必须是 Exception 或其子类
    • throws 必须抛出方法内部的所有异常对象,若存在继承关系,只需抛出其父类
    • 调用了一个声明抛出异常的方法,就必须处理此异常,若不处理
      • 要么继续抛出该异常
      • 要么 try...catch ```java package Pro.Exception;

import java.io.FileNotFoundException; import java.io.IOException;

public class DemoThrows { public static void main(String[] args) throws IOException { String fileName = “a.txt”; readFile(fileName); }

  1. public static void readFile(String fileName) throws IOException {
  2. if (!fileName.equals("a.txt")) {
  3. throw new FileNotFoundException("传递的文件不是a.txt");
  4. }
  5. if(!fileName.endsWith(".txt")){
  6. throw new IOException("文件后缀名不对!");
  7. }
  8. System.out.println("路径正确,读取成功!");
  9. }

}

  1. <a name="JnlRW"></a>
  2. ## 2.3 try...catch...finally
  3. - try中可能会抛出多个异常对象,就可以使用多个catch来处理异常
  4. - 如果try中产生了异常,就会执行catch中的异常处理逻辑
  5. - 如果try中未捕获到异常,不会中执行catch中的代码块
  6. - 无论是否捕获到异常,try...catch之后的代码都可以正常执行
  7. ```java
  8. package Pro.Exception;
  9. import java.io.FileNotFoundException;
  10. import java.io.IOException;
  11. public class DemoTryCatch {
  12. public static void main(String[] args) {
  13. String fileName = "b.txt";
  14. try{
  15. readFile(fileName);
  16. }catch (IOException e){
  17. System.out.println("文件路径不对!");
  18. }
  19. System.out.println("后续代码继续执行!");
  20. }
  21. public static void readFile(String fileName) throws IOException {
  22. if(!fileName.endsWith(".txt")){
  23. throw new IOException("文件后缀名不对!");
  24. }
  25. if(!fileName.equals("a.txt")){
  26. throw new FileNotFoundException("找不到文件" + fileName);
  27. }
  28. System.out.println("成功打开" + fileName);
  29. }
  30. }
  • finally 是无论是否出现异常都会执行的代码块
  • 注意事项

    • finally不可单独使用,必须与try一起使用
    • finally一般用于资源释放(回收),无论程序是否出现异常,最后都要资源释放(IO)

      2.4 获取异常信息

      Throwable 中定义了三个异常处理的方法
  • public String getMessage() 返回此throwable对象的简述

  • public String toString() 返回此throwable对象的详细信息
  • public void printStackTrace() JVM打印异常默认调用的方法

    2.5 多异常处理

    多异常处理有三种方式

  • 多个异常分别处理

  • 一次捕获,多次处理(一个try,多个catch)
    • catch里面定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上面,否则会报错
  • 一次捕获,同时处理
    • try中的所有异常可以归类为一个超类异常时方可使用 ```java package Pro.Exception;

import com.sun.tools.javac.util.List;

public class DemoMultiException { public static void main(String[] args) { int[] arr = {1, 2, 3}; List list = List.of(4, 5, 6); // // I.多个异常分别处理 // try{ // System.out.println(arr[3]); // }catch (ArrayIndexOutOfBoundsException e){ // System.out.println(e); // } // try{ // System.out.println(list.get(3)); // }catch (IndexOutOfBoundsException e){ // System.out.println(e); // }

  1. // I.一次捕获,多次处理

// try{ // System.out.println(arr[2]); // System.out.println(list.get(3)); // }catch(ArrayIndexOutOfBoundsException e){ // System.out.println(e); // }catch(IndexOutOfBoundsException e){ // System.out.println(e); // }

  1. // III.一次捕获,同时处理
  2. try{
  3. System.out.println(arr[3]);
  4. System.out.println(list.get(3));
  5. }catch(IndexOutOfBoundsException e){
  6. System.out.println(e);
  7. }
  8. System.out.println("后续代码");
  9. }

}

  1. <a name="jfWlK"></a>
  2. ## 2.6 子父类异常
  3. - 如果父类抛出了多个异常,子类重写父类方法时,有三种选择方式
  4. - 抛出和父类相同的异常
  5. - 抛出父类异常的子类
  6. - 不抛出异常
  7. - 如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,若子类产生异常,只能捕获处理
  8. <a name="IH8hu"></a>
  9. ## 2.7 自定义异常类
  10. ```java
  11. public class CustomException extends Exception/RuntimeException{
  12. public CustomException(){
  13. super();
  14. }
  15. public CustomException(String exceptionMessage){
  16. super(exceptionMessage);
  17. }
  18. }

3 多线程

4.1 并发与并行

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

    4.2 线程与进程

    概念

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

  • 线程 是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,也可以有多个线程

    线程调度

  • 分时调度

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

  • 抢占式调度

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

4.3 创建线程类

  • 主线程 执行主(main)方法的线程

创建新线程有两种方法

  • 将类声明为 java.lang.Thread 的子类,该子类重写Thread类的run方法,然后通过其实例启动一个线程
  • 声明实现 Runnable 接口的类,该类实现run方法,该类实现一个对象作为 Thread 类的构造参数,然后可以通过 start 启动一个线程

注意 : 多次启动同一个线程是非法的,特别是当一个线程已结束执行 ,不能再重新启动

创建线程的第一种方式

  1. package multithreading;
  2. public class MyThread extends Thread{
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 10; i++) {
  6. System.out.println("run " + i);
  7. }
  8. }
  9. }
  1. package multithreading;
  2. public class Demo01Thread {
  3. public static void main(String[] args) {
  4. MyThread mt = new MyThread();
  5. mt.start();
  6. for (int i = 0; i < 10; i++) {
  7. System.out.println("main " + i);
  8. }
  9. }
  10. }

创建线程的第二种方式

  1. package multithreading;
  2. public class SecondClock implements Runnable{
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 60; i++) {
  6. System.out.println(i + 1);
  7. try {
  8. Thread.sleep(1000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. }
  1. package multithreading;
  2. public class Demo02Thread {
  3. public static void main(String[] args) {
  4. SecondClock sc = new SecondClock();
  5. new Thread(sc).start();
  6. }
  7. }


Thread的方法

Thread的构造方法

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

Thread的常用方法

  • String getName() 返回该线程的名称
  • void setName(String name) 设置线程的名称
  • static Thread currentThread() 返回对当前正在执行的线程对象的引用
  • public static void sleep(long millis) 睡眠millis毫秒
  • public void start() 使此线程启动
  • public void run() 此线程要执行的任务

    两种方式的区别

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

  • 避免了单继承的局限性(一个类只能有唯一一个直接父类)

  • 增强了程序的扩展性,降低了程序的耦合性(解耦)

    • 实现Runnable,把设置线程任务和开启新线程进行了分离(提高代码复用性,进行资源共享)

      匿名内部类实现线程类

  • 继承 Thread 方式 ```java package multithreading;

public class DemoAnonymous { public static void main(String[] args) { new Thread(){ @Override public void run(){ System.out.println(“这是一个匿名内部类实现的线程”); } }.start(); } }

  1. - 实现 `Runnable` 接口方式
  2. ```java
  3. package multithreading;
  4. public class DemoAnonymous02 {
  5. public static void main(String[] args) {
  6. new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println("匿名接口对象实现多线程:" + Thread.currentThread().getName());
  10. }
  11. }).start();
  12. }
  13. }

4.4 线程安全

多个线程访问了共享数据 ,就会产生线程安全问题。 线程安全问题是应当避免的,可以让一个线程在访问共享数据是时,其他线程只能等待,而无论此线程是否抢占到CPU执行权。

线程同步

1.同步代码块
synchronized(锁对象){访问统一资源的代码块} 可以使用在某一代码块中,表示对此代码块执行线程访问的互斥操作

  • 同步代码块中的锁对象可以使用任意的对象
  • 必须保证多个线程使用的锁对象是同一个
  • 锁对象作用:只让一个线程在同步代码块中执行

该方法的缺陷 程序频繁地判断锁、获取锁、释放锁,使程序的效率降低

  1. package multithreading;
  2. public class SailTicket implements Runnable {
  3. private int ticketCount = 100;
  4. private Object obj = new Object();
  5. @Override
  6. public void run() {
  7. while (ticketCount > 0) {
  8. System.out.println("-------------------开始抢锁--------------------");
  9. synchronized (obj) {
  10. if (ticketCount > 0) {
  11. System.out.print(Thread.currentThread().getName() + "抢到了卖票权,");
  12. try {
  13. Thread.sleep(100);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println("正在出售第" + ticketCount + "张票");
  18. ticketCount--;
  19. }
  20. }
  21. }
  22. }
  23. }
  1. package multithreading;
  2. public class DemoSynchronized {
  3. public static void main(String[] args) {
  4. SailTicket run = new SailTicket();
  5. Thread t0 = new Thread(run);
  6. Thread t1 = new Thread(run);
  7. Thread t2 = new Thread(run);
  8. t0.start();
  9. t1.start();
  10. t2.start();
  11. }
  12. }

运行结果图
image.png
同步技术的原理 使用了一个锁对象,多个线程同时访问同步代码块时,谁抢到了锁对象谁就优先执行,其他线程则只能被阻塞,等待当前线程执行玩同步代码块后再重新开始抢夺使用权。

2.同步方法

格式:修饰符 synchronized 返回值类型 方法名(参数列表){访问共享数据的代码}

同步技术的原理 实际上也是利用锁对象,同步方法锁使用的锁对象是this,也就是调用线程的 Runnable 对象本身

  1. package multithreading;
  2. public class SailTicket02 implements Runnable {
  3. private int ticketCount = 100;
  4. @Override
  5. public void run() {
  6. while (ticketCount > 0) {
  7. sailTicket();
  8. }
  9. }
  10. private synchronized void sailTicket() {
  11. if(ticketCount > 0) {
  12. try {
  13. Thread.sleep(100);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println(Thread.currentThread().getName() + "抢到了售票权,正在出售第" + ticketCount + "张票");
  18. ticketCount--;
  19. }
  20. }
  21. }
  1. package multithreading;
  2. public class DemoSynchronized02 {
  3. public static void main(String[] args) {
  4. SailTicket02 run = new SailTicket02();
  5. Thread t0 = new Thread(run);
  6. Thread t1 = new Thread(run);
  7. Thread t2 = new Thread(run);
  8. t0.start();
  9. t1.start();
  10. t2.start();
  11. }
  12. }

3.静态同步方法

  • 将同步方法定义为静态方法,则静态同步方法的锁对象是本类的class属性(class文件对象)

    Lock锁

    Lock接口中的方法

  • void lock() 获取锁

  • void unlock() 释放锁

使用步骤

  • 使用 Lock 接口的实现类 ReentrantLock 创建一个对象
  • 在可能会出现安全问题的代码前调用 lock() 方法
  • 在可能会出现安全问题的代码后调用 unlock() 方法 ```java package multithreading;

import java.util.concurrent.locks.ReentrantLock;

public class SailTicket03 implements Runnable { private int ticketCount = 100; ReentrantLock l = new ReentrantLock();

  1. @Override
  2. public void run() {
  3. while (true) {
  4. l.lock();
  5. if (ticketCount > 0) {
  6. try {
  7. Thread.sleep(100);
  8. System.out.println(Thread.currentThread().getName() + "抢到了售票权,正在出售第" + ticketCount + "张票");
  9. ticketCount--;
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. } finally {
  13. l.unlock();
  14. }
  15. }
  16. }
  17. }

}

  1. ```java
  2. package multithreading;
  3. public class DemoLock {
  4. public static void main(String[] args) {
  5. SailTicket03 run = new SailTicket03();
  6. Thread t0 = new Thread(run);
  7. Thread t1 = new Thread(run);
  8. Thread t2 = new Thread(run);
  9. t0.start();
  10. t1.start();
  11. t2.start();
  12. }
  13. }

4.5 线程状态

线程的六种状态及转化

线程状态 导致状态发生的条件
NEW 至今尚未启动的线程处于这种状态
RUNNABLE 正在Java虚拟机中执行的线程
BLOCKED 受阻塞并等待某个监视器锁的线程
WAITING 无限期地等待另一个线程来执行某一特定操作的线程
TIMED_WATING 等待另一个线程来执行取决于指定等待时间的操作的线程
TERMINATED 已退出的线程

image.png

线程通信/等待唤醒

多个线程在处理同一个资源,但是处理的动作(线程的任务并不相同),线程之间就存在线程通信问题。

Object类中的方法

  • void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法之前,导致当前线程等待
  • void notify() 唤醒在此对象监视器上等待的单个线程,会继续执行wait方法之后的代码

无限期等待

  1. package multithreading;
  2. /*卖包子的案例*/
  3. public class WaitAndNotify {
  4. public static void main(String[] args) {
  5. Object obj = new Object();
  6. //创建一个顾客线程
  7. new Thread() {
  8. @Override
  9. public void run() {
  10. // 保证等待与唤醒只能有一个在执行,需要使用同步技术
  11. while (true) {
  12. synchronized (obj) {
  13. System.out.println("老板,我要买包子。");
  14. try {
  15. obj.wait();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. //执行wait之后的代码
  20. System.out.println("老板,你这包子真不错,我还要。");
  21. System.out.println("==============================");
  22. }
  23. }
  24. }
  25. }.start();
  26. //创建一个老板线程
  27. new Thread() {
  28. @Override
  29. public void run() {
  30. while (true) {
  31. try {
  32. Thread.sleep(1000);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. synchronized (obj) {
  37. System.out.println("小伙子,你的包子做好了。");
  38. obj.notify();
  39. }
  40. }
  41. }
  42. }.start();
  43. }
  44. }

程序执行的结果
image.png
计时等待

  • void sleep(long m) 方法
  • void wait(long m) 方法,若在m时间内未被notify唤醒,则线程自动醒来

唤醒方法

  • void notify() 如果有很多人等你做包子,你只做好了一个,你随便选一个给他们吃就行
  • void notifyAll() 比如说有很多个人在等你做包子,你给他们同时做好了,就可以叫他们一起来付钱(虽然包子是够的,但人性贪婪,顾客会争着抢着先付钱)

    4.6 线程池

  • 频繁地创建与销毁线程需要时间,如果一个线程执行很短的时间就结束了,就会大大降低系统的效率,因此引入 线程池 的概念

  • 当程序第一次启动的时候,就会创建多个线程,保存到一个集合中
  • 当想要使用线程时,就可以从集合中取出线程使用
  • 当线程使用完毕后,需要把线程归还给线程池

    java.util.concurrent.Executors JDK1.5之后的线程池工厂类

Executors 类中的静态方法

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

ExecutorService 接口中的方法

  • Future submit(Runnable task) ,提交一个task用于执行,并返回一个表示该任务的Future
  • void shutdown() 销毁线程池

    5 Lambda表达式

    Lambda的使用前提

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

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

    注:有且仅有一个抽象方法的接口称之为 函数式接口

先来一个lambda表达式开开眼界

  1. package LAMBDA;
  2. public class Demo01 {
  3. public static void main(String[] args) {
  4. //使用匿名内部类创建多线程
  5. new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName() + " created.");
  9. }
  10. }).start();
  11. //使用lambda表达式创建多线程
  12. new Thread(() -> System.out.println(Thread.currentThread().getName() + " created.")).start();
  13. }
  14. }

5.1 标准格式


Lambda省去面向对象的条条框框,格式由3个部分组成

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

其标准格式为
(参数类型 参数名称) -> {代码语句}
格式说明

  • ():接口中抽象方法的参数列表,无参数就留空,多个参数之间用逗号分隔
  • -> : 把参数传递给方法体{}
  • {} : 重写接口的抽象方法

    5.2 无参数无返回值

    给定一个厨子 Cook 接口,内含唯一的抽象方法 cookFood()

    1. public interface Cook {
    2. void makeFood();
    3. }

    使用lambda标准格式调用 invokeCook 方法 ```java public class DemoInvokeCook { public static void main(String[] args) {

    1. invokeCook(() -> {
    2. System.out.println("店里来客了,快去给我做饭啦!");
    3. });

    }

    private static void invokeCook(Cook cook) {

    1. cook.makeFood();

    } }

  1. <a name="BBiDU"></a>
  2. ## 5.3 有参数有返回值
  3. 需求描述
  4. - 使用数组存储多个Person对象
  5. - 调用 `Arrays.sort` 方法对数组中的人年龄进行升序排序
  6. ```java
  7. import java.util.Arrays;
  8. public class DemoPerson {
  9. public static void main(String[] args) {
  10. Person[] arr = {
  11. new Person("瓜兮兮", 18),
  12. new Person("哈搓搓", 28),
  13. new Person("憨包包", 20)
  14. };
  15. for (int i = 0; i < arr.length; i++) {
  16. System.out.println(arr[i]);
  17. }
  18. Arrays.sort(arr, (Person o1, Person o2) ->{
  19. return o1.getAge() - o2.getAge();
  20. });
  21. System.out.println("=======================");
  22. for (int i = 0; i < arr.length; i++) {
  23. System.out.println(arr[i]);
  24. }
  25. }
  26. }

给定一个计算器 Calculator 接口

  1. public interface Calculator {
  2. int calc(int a, int b);
  3. }

使用lambda标准格式计算两数之和

  1. public class DemoInvokeCalc {
  2. public static void main(String[] args) {
  3. invokeCalc(120, 130, (int a, int b) -> {
  4. return a + b;
  5. });
  6. }
  7. private static void invokeCalc(int a, int b, Calculator calculator) {
  8. int result = calculator.calc(a, b);
  9. System.out.println("结果是:" + result);
  10. }
  11. }

5.4 省略格式

Lambda表达式的内涵:凡是可以根据上下文推导出来的内容,都可以省略书写(可推导,可省略)
可以省略的内容有

  • 括号中参数列表的数据类型可以不写
  • 括号中的参数如果只有一个,那么类型和括号都可以省略不写
  • 如果{}中的代码只有一行,无论是否有返回值,那么可以{}, return以及分号都可以省略,但是三者必须同时省略