后端学习 Java笔记(附源码)补充

1 异常

1.1 异常概述
异常体系
Throwable
|____Error
|____Exception
     |____RuntimeException及其子类
     |____除RuntimeException之外所有的异常

Error:

  • 系统级别问题、JVM退出等,代码无法控制

Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题

  • RuntimeException及其子类:运行时异常,编译阶段不会报错。(空指针异常,数组索引越界异常)

  • 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。(日期格式化异常)

运行时异常
  • 数组索引越界异常:ArrayIndexOfBoundsException

  • 空指针异常:NullPointerException,直接输出没有问题,但调用空指针的变量的功能就会报错

  • 数字操作异常:ArithmetricException

  • 类型转换异常:ClassCastException

  • 数字转换异常:NumberFormatException

1.2 异常的处理机制

方式1:throws

不推荐

方式2:try...catch...

还可以,发生异常可以独立处理

Ctrl+Alt+T 使用固定格式抛出

格式

try {
    // 监视可能出现异常的代码
} catch (异常类型1 变量) {
    // 处理异常
} catch (异常类型2 变量) {
    // 处理异常
} ...

推荐格式

直接用Exception抛出所有异常

try {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date = simpleDateFormat.parse(data);
    System.out.println(date);
} catch (Exception e) {
    e.printStackTrace(); // 打印异常栈信息
}
方式3:两者结合
  • 方法直接将异常通过throws抛出给调用者

  • 调用者收到异常后直接捕获处理

public static void main(String[] args) {
    System.out.println("程序开始。。。");
    try {
        parseTime("2022-9-19 15:54:12");
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("程序结束。。。");
}
public static void parseTime(String data) throws Exception {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date = simpleDateFormat.parse(data);
    System.out.println(date);
}
  • 理论中第3种最好,但实际上能跑通就行

1.3 练习

public static void main(String[] args) {
    boolean flag = true;
    while (flag) {
        try {
            int value = input();
            if (value > 0) {
                flag = false;
            }
        } catch (Exception ignored) {
        }
    }
}
public static int input() {
    Scanner scanner = new Scanner(System.in);
    int value = Integer.parseInt(scanner.next());
    if (value <= 0) {
        value = Integer.parseInt("");
    }
    return value;
}

1.4 自定义异常

  1. 自定义编译时异常
  • 自定义一个异常继承类Exception

  • 重写构造器

  • 在出现异常的地方用throw new 自定义对象抛出

作用:编译时异常是编译阶段就报错,提醒更加强烈,即一定要处理

/**
 * 自定义编译时异常
 * 1.继承Exception
 * 2.重写构造器
 */
public class CookiesIllegalException extends Exception {
    public CookiesIllegalException() {
    }

    public CookiesIllegalException(String message) {
        super(message);
    }
}
public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            checkAge(36);
        } catch (CookiesIllegalException e) {
            e.printStackTrace();
        }
    }

    public static void checkAge(int age) throws CookiesIllegalException {
        if (age < 0 || age > 35) {
            // 抛出异常给调用者
            // throw:在方法内部直接创建一个异常对象并抛出
            // throws:用在方法申明上,提出方法内部的异常
            throw new CookiesIllegalException(age + "不符合规范");
        } else {
            System.out.println("符合");
        }
    }
}
  1. 自定义运行时异常
  • 定义一个异常继承类RuntimeException

  • 重写构造器

  • 在出现异常的地方用throw new自定义对象抛出

作用:提醒不强烈,编译阶段不报错,运行时才可能出现

/**
 * 自定义运行时异常
 * 1.继承RuntimeException
 * 2.重写构造器
 */
public class CookiesIllegalRuntimeException extends RuntimeException {
    public CookiesIllegalRuntimeException() {
    }

    public CookiesIllegalRuntimeException(String message) {
        super(message);
    }
}
public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            checkAge(36);
        } catch (CookiesIllegalRuntimeException e) {
            e.printStackTrace();
        }
    }

    public static void checkAge(int age) {
        if (age < 0 || age > 35) {
            // 抛出异常给调用者
            // throw:在方法内部直接创建一个异常对象并抛出
            // throws:用在方法申明上,提出方法内部的异常
            throw new CookiesIllegalRuntimeException(age + "不符合规范");
        } else {
            System.out.println("符合");
        }
    }
}

2 线程

多线程的创建>Thread类的常用方法>线程安全、线程同步>线程通信、线程池>定时器、线程状态等

2.1 线程创建方式一

方式一如何实现多线程?

  • 继承Thread

  • 重写run方法

  • 创建线程对象

  • 调用start()方法启动

优缺点

  • 优点:编码简单

  • 缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展

示例

/**
 * 1.定义一个线程类继承Thread类
 */
public class MyThread extends Thread {
    /**
     * 2.重写run方法,里面是定义线程以后要干啥
     */
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程执行:" + i);
        }
    }
}
/**
 * 目标:多线程的创建方式一:继承Thread类实现
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        // 3.new一个新线程对象
        Thread myThread = new MyThread();
        // 4.调用start方法启动线程(实际执行的还是run方法)
        myThread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行:" + i);
        }
    }
}
2.2 线程创建方法二

方式二如何实现多线程?

  • 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

  • 创建MyRunnable对象

  • MyRunnable对象交给Thread线程对象处理

  • 调用start()方法启动

优缺点

  • 优点:只是实现了Runnable接口,可以继续继承和实现

  • 缺点:如果线程有执行结果不能直接返回

/**
 * 1.定义一个线程任务类 实现Runnable接口
 */
public class MyRunnable implements Runnable{
    /**
     * 2.重写run方法,定义线程的执行任务
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行:" + i);
        }
    }
}
/**
 * 目标:学会线程的创建方式二,理解它的优缺点
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3.创建一个任务对象
        Runnable target = new MyRunnable();
        // 4.把任务对象交给Thread处理
        Thread thread = new Thread(target);
        // 5.启动线程
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行:" + i);
        }
    }
}

用匿名类写(另一种等价的编码方式)

/**
 * 目标:学会线程的创建方式二,理解它的优缺点
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3.创建一个任务对象
        // Runnable target = new MyRunnable();
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程执行:" + i);
                }
            }
        };
        // 4.把任务对象交给Thread处理
        Thread thread = new Thread(target);
        // 5.启动线程
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行:" + i);
        }
    }
}
2.3 线程创建方法三
方法名称说明
public FutureTask<>(Callable call)把Callable对象封装成FutureTask对象
public V get() throws Exception获取线程执行call方法返回的结果

优点:

  • 可以继承类可以实现接口

  • 可以在线程执行完毕后获取线程执行的结果

缺点:

代码麻烦

/**
 * 学会线程创建方式三:实现callable接口,结合FutureTask完成
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        // 3.创建callback任务对象
        Callable<String> call = new MyCallable(100);

        // 4.把callable交给FutureTask
        // FutureTask对象作用1:用于交给Thread
        // FutureTask对象作用2:通过get获取执行完成的结果
        FutureTask<String> futureTask = new FutureTask<>(call);

        // 5.交给线程处理
        Thread thread = new Thread(futureTask);

        // 6.启动线程
        thread.start();

        Callable<String> call2 = new MyCallable(100);
        FutureTask<String> futureTask2 = new FutureTask<>(call2);
        Thread thread2 = new Thread(futureTask2);
        thread2.start();

        try {
            // 如果FutureTask没有跑完,则会等待出结果
            String rs = futureTask.get();
            System.out.println("结果1:" + rs);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            String rs2 = futureTask2.get();
            System.out.println("结果2:" + rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 * 1.定义任务类,实现callable接口,应该声明线程任务结束后返回的类型
 */
class MyCallable implements Callable<String> {
    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    // 2.重写call方法
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i < n; i++) {
            sum += i;
        }
        return "子线程结果:" + sum;
    }
}
2.4 线程常用方法

线程的名称

setName()设置名称

getName()获得名称

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread thread1 = new MyTread("1号");
        thread1.start();

        Thread thread2 = new MyTread("2号");
        thread2.start();
    }
}

class MyTread extends Thread {
    public MyTread(String name) {
        // 为当前线程对象设置名称
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "子线程输出" +i);
        }
    }
}

sleep线程休眠

public class ThreadDemo5 {
    public static void main(String[] args) {
        Runnable target = new MyThreadDemo5();
        Thread thread = new Thread(target);
        thread.start();
    }
}

class MyThreadDemo5 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (i == 3) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("子线程运行:" + i);
        }
    }
}
2.5 线程同步
方法1:同步代码块
  • 作用:把出现线程安全的核心代码上锁

  • 原理:每次只能一个进程进入

synchronized(同步锁对象) {
    操作共享资源的代码(核心代码)
}

同步锁对象

推荐使用账户作为同步锁对象

例如:

  • 实例方法建议使用this作为锁对象

  • 静态方法建议使用字节码(类名.class)对象作为锁对象

public void drawMoney(int money) {
    // 0.获取取钱用户的姓名
    String name = Thread.currentThread().getName();
    // 同步代码块
    // 同步锁对象
    synchronized (this) {
        // 1.判断余额是否足够
        if (this.money >= money) {
            // 2.取钱
            System.out.println(name + "取钱" + money);
            // 3.更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余" + this.money);
        } else {
            // 4.余额不足
            System.out.println("余额不足");
        }
    }
}
方法2:同步方法

作用:把出现线程安全问题的核心方法给上锁

修饰符 synchronized 返回值类型 方法名称(形参列表) {
    操作共享资源的代码
}
public synchronized void drawMoney(int money) {
    
}

理论上同步代码块更好,但实际上同步方法用的更多

方法3:Lock锁

功能更强大,可自定义上锁和解锁

public class Account {
    private final Lock lock = new ReentrantLock();
    
    public void drawMoney(int money) {
        // 0.获取取钱用户的姓名
        String name = Thread.currentThread().getName();
        // 同步代码块
        // 同步锁对象
        // 1.判断余额是否足够
        lock.lock(); // 上锁
        try {
            if (this.money >= money) {
                // 2.取钱
                System.out.println(name + "取钱" + money);
                // 3.更新余额
                this.money -= money;
                System.out.println(name + "取钱后剩余" + this.money);
            } else {
                // 4.余额不足
                System.out.println("余额不足");
            }
        } finally {
            lock.unlock(); // 解锁
        }
    }
}
2.6 线程池

线程池就是一个可以复用线程的技术

不使用线程池的问题

用户每发起一个请求,后台就创建一个新线程来处理,严重影响系统性能

线程池API与参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingDeque<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

参数一:指定线程池的线程数量(核心线程):corePoolSize=====>不能小于0

参数二:指定线程池可支持的最大线程数:maximumPoolSize=====>最大数量 >= 核心线程数量

参数三:指定临时线程的最大存活时间:keepAliveTime=====>不能小于0

参数四:指定存活时间的单位(秒、分、时、天):unit=====>时间单位

参数五:指定任务队列:workQueue=====>不能为null

参数六:指定用哪个线程工厂创建线程:threadFactory=====>不能为null

参数七:指定线程忙,任务满的时候,新任务来了怎么办:handler=====>不能为null

处理Runnable任务

ExectorService的常用方法

方法名称说明
void execute(Runnable command)执行任务/命令,没有返回值,一般用于Runnable任务
Future<T> submit(Callback<T> task)执行Callback任务,返回未来任务对象获取线程结果

新任务拒绝策略

策略详解
ThreadPoolExector.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExector.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExector.CallerRunsPolicy由主线程负责调用任务的run()方法从而绕过线程池直接执行
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出" + i);
        }
        try {
            System.out.println("已休眠");
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 1.创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        // 2.给任务线程池处理
        Runnable target = new MyRunnable();
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
    }
}
处理Callable任务
public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) {this.n = n;}

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        return Thread.currentThread().getName() + "线程执行1~" + n + "之和的结果" + sum;
    }
}
/**
 * 目标:自定义一个线程池对象,并测试其特性
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1.创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        // 2.给任务线程池处理
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        System.out.println(f5.get());
    }
}
Executors得到线程池对象
方法名称说明
ExecutorService newCachedThreadPool()线程数量你随着任务增加而增加,如果线程执行完毕且空闲一段时间后会被回收
ExecutorService newFixedThreadPool(int nTheads)创建固定数量的线程池,如果某个线程因为执行异常而结束,那么会补充一个代替
ExecutorService newSingleThreadExecutor()创建一个线程的线程池,如果某个线程因为执行异常而结束,那么会补充一个代替
ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

大型项目一般不能用,容易出现资源不足的问题

public class ThreadPoolDemo3 {
    /**
     * 目标:使用Executors的工具方法直接得到一个线程池对象
     */
    public static void main(String[] args) throws Exception {
        // 1.创建固定线程数量的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
    }
}
定时器

Timer单线程,不推荐使用

public class TimerDemo1 {
    public static void main(String[] args) {
        // 1.创建Timer定时器
        Timer timer = new Timer(); // 定时器本身就是一个单线程
        // 2.调用方法,处理定时任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行一次");
            }
        }, 3000, 2000);
    }
}

ScheduleExecutor

通过线程池创造多个线程作为定时器

public class TimerDemo2 {
    public static void main(String[] args) {
        // 1.创建线程池
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);

        // 2.开启定时任务
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "输出" + new Date());
                try {
                    Thread.sleep(10000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);

        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "输出" + new Date());
            }
        }, 0, 2, TimeUnit.SECONDS);
    }
}

3 补充知识:单元测试(JUnit框架)

单元测试:针对最小功能单元测试,Java的最小功能单元是方法

JUnit 4.x常用注解

注解说明
@Test测试方法
@Before用来修饰实例方法,在每一个测试方法执行之前执行一次
@After用来修饰实例方法,在每一个测试方法执行之后执行一次
@BeforeClass用来静态修饰方法,会在所有测试方法之前只执行一次
@AfterClass用来静态修饰方法,会在所有测试方法之后只执行一次
public class UserService {
    public String loginName(String loginName, String password) {
        if ("admin".equals(loginName) && "123456".equals(password)) {
            return "登录成功";
        } else {
            return "用户名或密码错误";
        }
    }

    public void selectName() {
        // System.out.println(10/0);
        System.out.println("查询成功");
    }
}
public class TestUserService {
    @BeforeEach
    public void beforeEach() {
        System.out.println("===BeforeEach执行一次===");
    }

    @AfterEach
    public void afterEach() {
        System.out.println("===AfterEach执行一次===");
    }

    @BeforeAll
    public static void beforeAll() {
        System.out.println("===BeforeAll执行一次===");
    }

    @AfterAll
    public static void afterAll() {
        System.out.println("===AfterAll执行一次===");
    }

    /**
     * 测试方法
     * 注意点:
     * 1.必须是公开的、无参数、无返回值的方法
     * 2.测试方法必须使用@Test注解
     */
    @Test
    public void testLoginName() {
        UserService userService = new UserService();
        String result = userService.loginName("admin", "123456");

        // 进行预期结果的正确性测试:断言
        Assertions.assertEquals("登录成功", result, "可能出现问题");
    }

    @Test
    public void testSelectName() {
        UserService userService = new UserService();
        userService.selectName();
    }
}

3 反射

3.1 反射概述

对于任何一个Class类,在运行的时候都可以直接得到这个类全部成分

反射第一步

获取Class类对象

获取Class类的对象的三种方式

  1. Class c1 = Class.foeName("全类名");

  2. Class c2 = 类名.class

  3. Class c3 = 对象.getClass()

public static void main(String[] args) throws Exception {
    // 1.获取Class类中的一个静态方法:forName
    Class<?> c = Class.forName("com.cookies.a01_reflect.Student");
    System.out.println(c); // Student.class
    // 2.类名.class
    Class<Student> c1 = Student.class;
    System.out.println(c1);
    // 3.对象.getClass() 获取对象对应类的Class对象
    Student s = new Student();
    Class<? extends Student> c2 = s.getClass();
    System.out.println(c2);
}
3.2 反射获取Constructor、Field、Method对象
public class TestStudent01 {
    // 1.getConstructors:
    // 获取全部的构造器,只能获取public修饰的构造器
    // Constructor[] getConstructors()
    @Test
    public void getConstructors() {
        // a.获取类对象
        Class c = Student.class;
        // b.提取类中的全部的构造器对象
        Constructor[] constructors = c.getConstructors();
        // c.遍历构造器
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
        }
    }

    // 2.getDeclaredConstructors:
    // 获取全部的构造器
    // Constructor[] getDeclaredConstructors()
    @Test
    public void getDeclaredConstructors() {
        // a.获取类对象
        Class c = Student.class;
        // b.提取类中的全部的构造器对象
        Constructor[] constructors = c.getDeclaredConstructors();
        // c.遍历构造器
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
        }
    }
}
3.3 反射的作用
public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        // 通过反射实现不同类型元素的加入
        ArrayList<String> list1 = new ArrayList<>();
        ArrayList<Integer> list2 = new ArrayList<>();

        System.out.println(list1.getClass());
        System.out.println(list2.getClass());

        System.out.println(list1.getClass() == list2.getClass());

        System.out.println("======");
        ArrayList<Integer> list3 = new ArrayList<>();
        list3.add(11);
        list3.add(22);
        list3.add(33);

        Class c = list3.getClass();
        Method add = c.getDeclaredMethod("add", Object.class);
        // 定位c类中的add方法
        boolean rs = (boolean) add.invoke(list3, "黑马");
        System.out.println(rs);

        System.out.println(list3);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值