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 自定义异常
- 自定义编译时异常
-
自定义一个异常继承类
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("符合");
}
}
}
- 自定义运行时异常
-
定义一个异常继承类
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类的对象的三种方式
-
Class c1 = Class.foeName("全类名");
-
Class c2 = 类名.class
-
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);
}
}