目录
[34.1 理解线程](#34.1 理解线程)
[34.2 线程的几种状态](#34.2 线程的几种状态)
[34.3 实现线程的过程](#34.3 实现线程的过程)
[34.4 模拟火车站售票](#34.4 模拟火车站售票)
[35.1 生产消费者模型](#35.1 生产消费者模型)
(2)解决资源抢夺synchronized和不加安全锁时产生的异常
[36.1 join方法](#36.1 join方法)
[36.2 死锁](#36.2 死锁)
[36.3 线程应用---定时器](#36.3 线程应用---定时器)
[37.1 反射技术reflect编辑](#37.1 反射技术reflect编辑)
[① 反射机制修改String字符串](#① 反射机制修改String字符串)
[三十八、反射机制之方法 和 构造方法](#三十八、反射机制之方法 和 构造方法)
[38.1 Method类](#38.1 Method类)
[38.2 Constructor 类中的构造方法](#38.2 Constructor 类中的构造方法)
[38.3 设计控制反转IOC](#38.3 设计控制反转IOC)
[三十九、注解(初始 + 元注解)](#三十九、注解(初始 + 元注解))
[39.1 注解Annotation](#39.1 注解Annotation)
[39.2 注解(反射注解 + 注解应用)](#39.2 注解(反射注解 + 注解应用))
三十四、线程基础
34.1 理解线程
1、程序:可以理解为一组静态的代码
2、进程:正在进行的程序,静态的代码,运行起来
3、线程:正在执行的程序中的小单元
4、例子:播放器
(1)比如你打开一个播放器,这个播放器正在运行这,这个我们称之为进程;
(2)你下载这个播放器,下载到我们电脑的硬盘里,下载完后里面有很多代码,我们称之为程序;
(3)当我们正在播放视频的时候,视频正在播放,当你调整声音大小的时候,我们的视频是不会卡住的,其中你调声音这个功能就是线程
(4)一个进程里有很多个小线程,在帮我做事情,是同步的。
5、例子:聚餐
(1)一个老师要和同学举行聚餐,在聚餐之前,班级要大扫除,扫除需要分配任务,任务写在纸上,列一个清单(这时候扫除这件事并没有开始执行,静态的);当老师一声令下,开始扫除(这时候执行开始,就是进程);好多个人每一个人做一件事,互相并不影响,每一个同学都做自己的事情,并发执行,互不影响(学生做事就是线程);
6、主线程---系统线程;
7、用户线程---相当于我们自己写的事情main;
8、守护线程(也叫精灵线程)---GC
9、线程-----属于操作系统级别
34.2 线程的几种状态
1、创建线程------就绪状态(线程已经准备好了,随时可以执行,但是未执行)------执行状态(线程正在执行)-----等待状态
(1)等待状态又称为挂起状态,其实就是休息一会;
(2)好比你打扫屋子累了歇一会,有的累了就不打扫了,有的就歇一会准备继续打扫,也就是就绪状态和异常/死亡
34.3 实现线程的过程
(1)实现线程第一种方法
1、自己描述一个类(怎么体现?就是继承一个类让他成为你想描述的类)
2、继承父类Thread
3、在自己的类里面重写run方法
4、创建一个对象(new一个线程对象)
5、调用start方法,为了让线程进入就绪状态
6、例子:实现一个跑步的小例子,比赛的时候,多个人同时跑步
java
package testthread;
/**
* @Description: TODO 1、2、继承父类Thread变为线程类
* @Author: 曹宇希
* @Date: 2023/11/10 10:49
* @Version: 1.0
* @Company: 版权所有
*/
public class RunningMan extends Thread{
private String name;
public RunningMan(){}
public RunningMan(String name) {
this.name = name;
}
/**
* @Description: TODO 3、重写run方法
* @Author: 曹宇希
* @Date: 2023/11/10 10:50
* @Param: void
* @Return: void
*/
@Override
public void run() {
for (int i = 1; i < 20; i++) {
System.out.println(this.name + "跑了" + i + "米");
}
}
}
java
package testthread;
public class TestMain {
public static void main(String[] args) {
//4、创建一个线程对象
RunningMan r1 = new RunningMan("张三");
RunningMan r2 = new RunningMan("李四");
RunningMan r3 = new RunningMan("王五");
//5、调用start方法,让线程进入就绪状态(从Thread类中继承过来的)
//不能直接调用run,如果调用了就是单线程了(按顺序逐个执行),就不是CPU管理的了
//每一次跑的顺序都不一样,3个人(线程)同一时间一起执行,每个线程执行一会就有可
//能被CPU夺走了,然后去执行别的线程,然后有可能在过一会在回来执行,也可能不执行
r1.start();
r2.start();
r3.start();
}
}
7、但是会发现一个问题,我们Java中只能实现单继承,什么意思呢?
(1)假设是一个Person类它已经继承了Animal类了,但是我还想让这个Person类继承Thread,去做线程,这时候问题就来了!所以还有第二种方法
(2)实现线程第二种方法
1、自己描述一个类
2、实现一个父接口Runnable
3、重写run方法
4、new一个线程对象
34.4 模拟火车站售票
1、例子:车票
(1)属性:起始站、终点站、票价
(2)利用Vector集合,线程安全,多个线程并发操作同一个集合的时候,不会抢夺资源
java
package system12306;
/**
* @Description: TODO 车票类
* @Author: 曹宇希
* @Date: 2023/11/10 14:18
* @Version: 1.0
* @Company: 版权所有
*/
public class Ticket {
//User类 Ticket类没有任何逻辑,只是包含一些基本属性。就是一个小容器,就为了提供阅读性
//一个对象,包含很多属性,增强可读性,类名、属性名、属性类型不一致POJO(也可以叫JavaBean)
//起始站
private String start;
//终点站
private String end;
//票价,Double一定要写大写,小写不能存空null
private Double price;
public Ticket(String start, String end, Double price) {
this.start = start;
this.end = end;
this.price = price;
}
public String getStart() {
return start;
}
public void setStart(String start) {
this.start = start;
}
public String getEnd() {
return end;
}
public void setEnd(String end) {
this.end = end;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
//为了让打印输出时不是hashcode码,重写toString
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
builder.append(this.start);
builder.append("----->");
builder.append(this.end);
builder.append(":");
builder.append(this.price);
builder.append("]");
return builder.toString();
}
}
java
package system12306;
import java.util.Vector;
/**
* @Description: TODO 售票系统
* @Author: 曹宇希
* @Date: 2023/11/10 14:18
* @Version: 1.0
* @Company: 版权所有
*/
public class System12306 {
//要将信息存储一个容器里,后期可能加火车的班次,所以选集合最好
//vector线程更安全
private Vector<Ticket> tickets = new Vector<>();
//当前系统创建后给tickets集合赋值
{
for (int i = 10; i < 30; i++) {
tickets.add(new Ticket("北京"+ i + "站到","深圳" + i +"站, 价格为:",(i*50.00)*12 ));
}
}
//从集合内获取一张火车票
public Ticket getTicket() {
//每一次哪个窗口想卖,窗口就从我这个系统拿一张票就出去了
try {
//每次只取第一张,就是来一个人取一张
return tickets.remove(0);
//如果没有票了,这个地方就越界了,所以要抛异常
} catch (Exception e) {
//如果产生异常了,说明没有票了,那就是空了
return null;
}
}
//火车站有很多张票,所以这系统里面的票都是我们挨个new的,但是这个系统就一个啊
//拿怎么保证就一个系统啊
//单例模式
private System12306(){}
//饿汉式
private static System12306 sys = new System12306();
//提供共有的方法
public static System12306 getInstance(){
return sys;
}
}
java
package system12306;
/**
* @Description: TODO 窗口线程
* 多个窗口同时操作,每一个窗口可以同时操作
* @Author: 曹宇希
* @Date: 2023/11/10 14:18
* @Version: 1.0
* @Company: 版权所有
*/
public class Window extends Thread{
//窗口名
private String windowName;
public Window(String windowName) {
this.windowName = windowName;
}
//run,是CPU管理的,我们一定要重写run,然后CPU调用run。所以程序是从这里执行的
@Override
public void run() {
this.sellTicket();
}
//卖票
public void sellTicket() {
//这个窗口只要这个窗口不关闭就一直卖
while (true) {
//票从系统里来,单例模式的化,返回值正好是个系统的对象,不管哪个窗口你获取的对象都是同一个
System12306 sys = System12306.getInstance();
Ticket ticket = sys.getTicket();//从Vector集合中获取一张票
if (ticket == null) {//证明卖完了
System.out.println("对不起" +windowName + "当前窗口车票以售完");
break;
}
System.out.println("从" + windowName + "卖出一张" + ticket);
}
}
}
java
package system12306;
/**
* @Description: TODO 测试输出
* @Author: 曹宇希
* @Date: 2023/11/10 15:21
* @Version: 1.0
* @Company: 版权所有
*/
public class TestMain {
public static void main(String[] args) {
//不用new系统了,因为窗口里有系统,每个人都是一个系统
Window w1 = new Window("北京1站");
Window w2 = new Window("北京2站");
Window w3 = new Window("北京3站");
w1.start();
w2.start();
w3.start();
}
}
三十五、线程之生产消费者模型
35.1 生产消费者模型
(1)线程的资源抢夺问题
**资源抢夺:**为了研究是否抢占资源,两个对象同时操作一个仓库,一个生产者和一个消费者不会抢资源,当多个消费者,一个生产者就会出现资源抢夺问题。出现索引越界问题
java
package producer;
import java.util.ArrayList;
/**
* @Description: TODO 仓库
* @Author: 曹宇希
* @Date: 2023/11/10 16:27
* @Version: 1.0
* @Company: 版权所有
*/
public class Warehouse {
//仓库里面的集合用来存放元素
private ArrayList<String> list = new ArrayList<>();
//集合目的为了装东西和拿东西,只有仓库自己才能操作自己的集合
//向集合内添加元素的方法,
public void add() {
//只有生产者往仓库里放东西,但是生产者不能一直往里放,不歇会一直干累死了
if (list.size()<20) {//假设
list.add("a");
} else {
//有点像break,相当于让方法执行到这里就结束方法了,什么也不返回
return;
}
}
//从集合内获取元素,消费者拿东西
public void get() {
//只有消费者往仓库里拿东西,但是消费者不能一直拿
if (list.size()>0) {//假设
//每次都拿第一个
list.remove(0);//有可能出现越界的问题,当只剩下一个资源的时候,有多个消费者拿就会产生此问题
} else {//越界就把不拿了
//有点像break,相当于让方法执行到这里就结束方法了,什么也不返回
return;
}
}
}
java
package producer;
/**
* @Description: TODO 生产者
* @Author: 曹宇希
* @Date: 2023/11/10 16:27
* @Version: 1.0
* @Company: 版权所有
*/
public class Producer extends Thread{
//为了保证生产者 和 消费者使用同一个仓库对象,添加一个属性聚合关系(应该是依赖关系,从关系的角度单例模式好一点)
private Warehouse house;
public Producer(Warehouse house) {
this.house = house;
}
//生产者的run方法,一直向仓库内添加元素
@Override
public void run() {
//一直放
while (true) {
//放的过程中,只有仓库自己才能操作自己的属性,所以肯定需要仓库对象点add方法
//但是不能创建仓库对象,因为生产者和消费者要在同一个地方获得/取走东西,怎么
//保证这两个人是在一个仓库呢?,单例模式 或 将仓库传递过来 或 依赖关系。
//传参肯定不行因为run是我们重写的,父类是有run无参的,如果这里给run添加参数就不是重写了
//方法重写,必须保证继承关系,名字、参数列表必须与父类一致
house.add();
System.out.println("生产者存入了一件货物");
try {//可以让他休息一会
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//设计单例模式
}
java
package producer;
/**
* @Description: TODO 消费者
* @Author: 曹宇希
* @Date: 2023/11/10 16:27
* @Version: 1.0
* @Company: 版权所有
*/
public class Consumer extends Thread{
//为了保证生产者 和 消费者使用同一个仓库对象,添加一个属性聚合关系
private Warehouse house;
public Consumer(Warehouse house) {
this.house = house;
}
//消费者方法,一直从仓库内获取元素
public void run() {
while (true) {
house.get();
System.out.println("消费者拿走了一件货物");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
java
package producer;
/**
* @Description: TODO
* @Author: 曹宇希
* @Date: 2023/11/11 10:39
* @Version: 1.0
* @Company: 版权所有
*/
public class TestMain {
public static void main(String[] args) {
Warehouse house = new Warehouse();//里面有个ArrayList线程非安全
Producer p = new Producer(house);
Consumer c = new Consumer(house);
Consumer c2 = new Consumer(house);
p.start();
c.start();
c2.start();
}
}
(2)解决资源抢夺synchronized和不加安全锁时产生的异常
1、通过这个模型,成功的演示出了,线程安全的问题
(1)两个消费者,同时访问同一个仓库对象,仓库内只有一个元素的时候,两个消费者并发访问,会有可能产生抢夺资源的问题
2、为了解决此问题,自己解决一下线程安全的问题
(1)在仓库对象被线程访问的时候,让仓库对象被锁定(当仓库只有一个元素的时候,只允许一个人进入,这个进入之后就把仓库锁死,不让其他人进入),当前的仓库对象只能被一个线程访问,其他的线程处于等待状态
(2)利用特征修饰符synchronized(同步或线程锁),同一个时间点只有一个线程访问;
(3)synchronized有两种形式写法
第一种: 将synchronized关键字,放在方法的结构上public synchronized void get(){ } ,它锁的不是方法是对象,锁定的是调用方法时的那个对象(哪个对象调用,就锁哪个对象),是访问这个对象的线程锁定了他;
**第二种:**将synchronized关键字,放在方法(构造方法 块)的内部,无论写在哪,锁定的都是对象
public void get(){
很多代码...
synchronized( 对象 ){ 很多代码... }
很多代码...
}
就是当调用这个方法的时候不会锁定,只有执行到synchronized的时候才会被锁定,提供性能,锁定当前对象就在synchronized(this),锁定其他对象就写你想锁定的对象
这个形式,除了可以锁定当前对象以外还可以锁定其他对象。
**第一种:**在Warehouse中写synchronized,只有仓库自己才能操作自己的属性,所以这时候锁定的是仓库
java
//从集合内获取元素,消费者拿东西
public synchronized void get() {//add方法一样写
//只有消费者往仓库里拿东西,但是消费者不能一直拿
if (list.size()>0) {//假设
//每次都拿第一个
list.remove(0);//有可能出现越界的问题,当只剩下一个资源的时候,有多个消费者拿就会产生此问题
} else {//越界就把不拿了
//有点像break,相当于让方法执行到这里就结束方法了,什么也不返回
return;
}
}
3、我们觉得Warehouse仓库中的return不是很好,应该是让线程的不同状态来回切换,执行或等待;
wait(); 对象.wait();不是当前的这个对象wait,他是访问当前这个对象的线程wait;比我我们在仓库的add方法中this.wait();不是仓库在等待,而是访问仓库对象的线程在等待;所以this.wait()而是访问仓库的生产者线程进入等待状态;
java
//向集合内添加元素的方法,
public void add() {//get也是一样的添加wait方法
//只有生产者往仓库里放东西,但是生产者不能一直往里放,不歇会一直干累死了
if (list.size()<20) {//假设
list.add("a");
} else {
try {
//仓库调用wait方法,不是仓库对象处于等待,而是访问仓库的生产者线程进入等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此时会发现生产者和消费者都处于等待状态了,但是如果都等待了就没人干活了;所以生产者看够一定数量了就处于等待,让消费者工作,当消费者拿走了一大部分这时,就该让生产者生产了。是相互维持的关系,所以还要让对方唤醒notifyall()
这时就完全解决了抢夺问题,同时还解决了偷懒不干活的问题。
java
//向集合内添加元素的方法,
public void add() {
//只有生产者往仓库里放东西,但是生产者不能一直往里放,不歇会一直干累死了
if (list.size()<20) {//假设
list.add("a");
} else {
try {
this.notifyAll();
//仓库调用wait方法,不是仓库对象等待,是范围跟仓库的生产者线程进入等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//从集合内获取元素,消费者拿东西
public synchronized void get() {
//只有消费者往仓库里拿东西,但是消费者不能一直拿
if (list.size()>0) {//假设
//每次都拿第一个
list.remove(0);
} else {
try {
this.notifyAll();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
但是你会发现这个效率还是低,所以我们采用设置设置线程的优先级别1~10层次
java
public class TestMain {
public static void main(String[] args) {
Warehouse house = new Warehouse();//里面有个ArrayList线程非安全
Producer p = new Producer(house);
//设置线程的优先级别1~10层次
p.setPriority(10);//让生产者优先弄资源
Consumer c = new Consumer(house);
Consumer c2 = new Consumer(house);
p.start();
c.start();
c2.start();
}
}
还是产生了IllegalMonitorStateException异常
仓库对象.wait(); 是访问仓库的生产者线程等待,当仓库满了的时候,仓库对象会告诉生产者线程说你给我等一会,但是在告诉生产者的这一刹那,因为是多线程并发的原因,有可能这一刹那 来的就不是你想告知的那个原有的生产者线程,是其他的线程,此时就会产生IllegalMonitorStateException异常
IllegalMonitorStateException通俗讲就是让其等待的程序对象不是当前程序的所有者,而是其他线程
如果不加安全锁会产生:IllegalMonitorStateException 和 索引越界的两个问题
(3)生产消费者模型总结
通过上述的生产消费者模型,做一个非常完整而且安全的模型
1、利用线程安全锁,特征修饰符sysnchronized,有两种不同的写法,不管怎么写写,锁定的永远是对象
2、利用方法控制线程状态的来回切换 wait() notify() notifyAll() 这三个方法都是Object类中的方法
3、Thread类中的方法
sleep() 静态方法,通过类名调用,参数是long类型的
setPriority() 设置 / 获取线程优先级的1~10层级,数字越高优先级越高,更加容易获取CPU分配的资源碎片
4、考察线程
(1)线程的创建方式
(2)程序 进程 线程 的概念区别
(3)线程的几种状态,如果切换
(4)sleep方法,Thread类,是通过静态类名.的形式调用,哪个位置调用哪个线程就等待,唤醒时间到了自己就唤醒了,不会释放锁
wait方法,Object类,是通过对象.的形式调用,对象调用方法,是访问对象的其他线程等待,唤醒需要其他对象调用notify唤醒,等待后会释放锁
三十六、join方法和死锁
36.1 join方法
join:
(1)在Thread类中,可以让两个线程并行变成单线程,就是前后问题
线程创建好了之后,我们不能控制线程进入的顺序,我们只能让线程进入就绪状态,由CPU分配run
想让ThreadTwo线程join到ThreadOne里面去。
(1)设计模型让two线程加入到One线程
1、有两个线程One Two ,two加入到one里面
2、设计模型的时候,two线程在one的run里面创建,保证两个有先后顺序
3、two.join(); 无参数===0,有参数假设我们给他设定参数==2000
java
//如果无参数,它的底层join相当于走的,这个isAlive()就是this.isAlive()
two.join(0){
while (isAlive()) {//相当于two.isAlive()
wait(0);//相当于this.wait(0)表示一直等,访问two对象的线程进入等待状态,就是one进入等待
}
}
java
//如果有参数,
two.join(2000){
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
最关键的是join有锁synchronized
(1)two想要join的时候,One允许他加入
(2)two执行完毕
java
package testjoin;
public class ThreadOne extends Thread{
//线程1开始跑的时候,线程2才开始创建,所以我们认为线程1比线程2早(已经控制顺序)
@Override
public void run() {
System.out.println("ThreadOne--start");
ThreadTwo two = new ThreadTwo();
two.start();
try {
//线程2加入线程1里面
two.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadTwo--end");
}
}
java
package testjoin;
public class ThreadTwo extends Thread {
@Override
public void run() {
System.out.println("ThreadTwo--start");
try {
ThreadTwo.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadTwo--end");
}
}
join方法也是线程锁;
(2)模型Two线程加入One线程,Three锁Two线程
在One线程开始执行的时候,Two加入到了One的线程内,One是2秒,Two是5秒;所以说One等着Two执行2000毫秒之后,觉得Two太慢了需要5秒,想把Two踢出去;
One线程中的join(2000),就是只给Two2000毫秒的时间,但是Two是5000毫秒,所以这时One线程就不让Two在执行了,One等了Two2000毫秒以后,想把Two踢出去,然后One线程自己执行;
One等two2秒后,想把5秒的two踢出去,这时Three锁定了two线程1万毫秒,就是你想踢的时候,two被three拿走了,这时one就一直等着three把这个two还回去才能踢
顺序:
1、One启动
2、two启动
3、Three启动
4、two就join啦
5、2000之后,one想要将two从自己的线程内剔除
6、two对象不在自己的手里,对象被three线程锁定啦,10000毫秒
7、one只能等待,three将two对象释放后,才能踢掉
java
public class ThreadOne extends Thread{
//线程1开始跑的时候,线程2才开始创建,所以我们认为线程1比线程2早(已经控制顺序)
@Override
public void run() {
System.out.println("ThreadOne--start");
ThreadTwo two = new ThreadTwo();
two.start();
try {
//线程2加入线程1里面
two.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadTwo--end");
}
}
java
public class ThreadTwo extends Thread {
@Override
public void run() {
System.out.println("ThreadTwo--start");
ThreadThree three = new ThreadThree(this);
three.start();
try {
ThreadTwo.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadTwo--end");
}
}
java
public class ThreadThree extends Thread {
private ThreadTwo two;
public ThreadThree(ThreadTwo two) {
this.two = two;
}
//模拟:在two执行的过程中,one处于等待状态,这时three将two对象锁定
public void run() {
System.out.println("ThreadThree--start");
//我想锁two这个里面就写two对象,但是three对象里没有two,那我们new一个对象?
//肯定不行,因为我想锁的是One线程里的two线程,如果new就创建新的线程了,就不是刚
//刚那个线程了,那我能传参嘛,也不行因为run是重写Thread的方法,所以加属性
synchronized (two) {
System.out.printf("two is locked");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ThreadThree--end");
}
}
java
public class TestMain {
public static void main(String[] args) {
ThreadOne one = new ThreadOne();
one.start();
}
}
36.2 死锁
syschronized锁,非常厉害,一旦对象被锁定,不释放的情况下,其他的对象都需要等待,有可能会产生一个死锁的效果
(1)模拟死锁哲学家就餐问题
有4个哲学家a\b\c\d,没一个人先拿左手,再拿右手,才能吃饭
java
public class Philosopher extends Thread {
private String pname;//哲学家名字
private Chopstick left;
private Chopstick right;
public Philosopher(String pname, Chopstick left, Chopstick right) {
this.pname = pname;
this.left = left;
this.right = right;
}
@Override
public void run() {
synchronized (left) {
System.out.println(this.pname + "拿起了左手边的块子" + this.left.getNum() + "筷子");
synchronized (right) {
System.out.println(this.pname + "拿起了右手边的块子" + this.right.getNum() + "筷子");
System.out.println(this.pname + "开始吃饭");
}
}
}
}
java
public class Chopstick {
private int num;
public Chopstick(int num) {
this.num = num;
}
public int getNum() {
return this.num;
}
}
(2)解决死锁的问题
1、要礼让-----就是让他们产生时间差
2、不要产生对象公用的问题
java
package philosopher;
/**
* @Description: TODO 哲学家就餐问题
* @Author: 曹宇希
* @Date: 2023/11/11 16:55
* @Version: 1.0
* @Company: 版权所有
*/
public class Philosopher extends Thread {
private String pname;//哲学家名字
private Chopstick left;
private Chopstick right;
private long time;
public Philosopher(String pname, Chopstick left, Chopstick right, long time) {
this.pname = pname;
this.left = left;
this.right = right;
this.time = time;
}
@Override
public void run() {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (left) {
System.out.println(this.pname + "拿起了左手边的块子" + this.left.getNum() + "筷子");
synchronized (right) {
System.out.println(this.pname + "拿起了右手边的块子" + this.right.getNum() + "筷子");
System.out.println(this.pname + "开始吃饭");
}
}
}
}
java
package philosopher;
public class TestMain {
public static void main(String[] args) {
Chopstick c1 = new Chopstick(1);
Chopstick c2 = new Chopstick(2);
Chopstick c3 = new Chopstick(3);
Chopstick c4 = new Chopstick(4);
Philosopher a = new Philosopher("a",c2,c1,1000);
Philosopher b = new Philosopher("b",c3,c2,2000);
Philosopher c = new Philosopher("c",c4,c3,3000);
Philosopher d = new Philosopher("d",c1,c4,4000);
a.start();
b.start();
c.start();
d.start();
}
}
但是这个效果很慢,怎么提高效率,你会发现相对的位置是没有冲突的,所以可以让相对位置的时间相同;
让ac he bd同时吃
java
public class TestMain {
public static void main(String[] args) {
Chopstick c1 = new Chopstick(1);
Chopstick c2 = new Chopstick(2);
Chopstick c3 = new Chopstick(3);
Chopstick c4 = new Chopstick(4);
Philosopher a = new Philosopher("a",c2,c1,0);
Philosopher b = new Philosopher("b",c3,c2,2000);
Philosopher c = new Philosopher("c",c4,c3,0);
Philosopher d = new Philosopher("d",c1,c4,2000);
a.start();
b.start();
c.start();
d.start();
}
}
36.3 线程应用---定时器
定时器/计时器----> 线程应用
java.util包中的
(1)模拟垃圾短信
设计一个垃圾短信,每隔一段时间,发送一些数据
schedule( TimerTask task , Date time); task就是一个小任务,time表示的是起始时间
schedule( TimerTask task , long delay ,long period);period表示周期,就是多长时间在干一次;delay 表示给的一个延时时间
schedule( TimerTask task , Date firstTime , long period) ;firstTime 表示你给定的时间开始
java
package testtimer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @Description: TODO 定时器
* @Author: 曹宇希
* @Date: 2023/11/11 17:37
* @Version: 1.0
* @Company: 版权所有
*/
public class TestTimer extends Thread{
private int count = 1;
//集合--存储所有人的 电话号-账户
private ArrayList<String> userBox = new ArrayList<>();
//存储
{
userBox.add("a");
userBox.add("b");
userBox.add("c");
userBox.add("d");
}
//方法;每隔一段时间,发送一些数据
public void test() {
System.out.println("准备发送");
//相当于启动了一个小线程,他帮我做记录,每隔一段时间,就做一件事情
Timer timer = new Timer();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = null;
try {
firstTime = sdf.parse("2023-11-11 19:35:20");//只有到了这个时间才会发送,过早,过晚都不行
} catch (ParseException e) {
e.printStackTrace();
}
//参数
timer.schedule(new TimerTask() {
@Override
public void run() {
for (int i = 0; i < userBox.size(); i++) {
System.out.println("给" + userBox.get(i) + "发送了" + count + "条消息:垃圾短信");
count++;
}
System.out.println("垃圾短信已经发送完毕");
}
}, firstTime, 2000);
}
public static void main(String[] args) {
TestTimer testTimer = new TestTimer();
testTimer.test();
}
}
三十七、反射机制
37.1 反射技术reflect
类是用来描述一组对象的,反射机制认为是用来描述一组类的,可以与之前学习File对比者学习
**Class:**用来描述类本身
**Package:**描述类所属的包
**Field:**用来描述类中的属性
**Method:**描述类中的方法
**Constructor:**描述类中的构造方法
**Annotation:**描述类中的注解
(1)Class类相关
1、如何获取Class
(1)Class clazz = Class.forName(" 包名.类名 ");
(2)类名.class;
(3)Class clazz = 对象的引用.getClass(); Object类中的方法
2、Class中常用的方法
(1)int result = getModifiers(); 获取类的修饰符(权限、特征)
java
//通过一个Class对象来操作Person.class类文件
try {
Class clazz = Class.forName("testreflect.Person");
//类自己有结构:特征修饰符、权限修饰符、类名字、继承、实现
//将每一个修饰符,用一个整数来表示,是从0开始0 1 2 4 8 16每个整数都表示修饰符
int modifiers = clazz.getModifiers();
System.out.println(modifiers);//1表示public,当Person是public和abstract,返回他们表示所表示的数的和
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
java
public class Person {
}
(2)String name = clazz.getName(); 获取名字
String simpleName = clazz.getSimpleName(); 获取简单名
java
Class clazz = Class.forName("testreflect.Person");//在try中写
//类的名字
String name = clazz.getName();
String simpleName = clazz.getSimpleName();
System.out.println(name);//类全名 testreflect.Person
System.out.println(simpleName);//只有名字 Person
(3)Package p = clazz.getPackage(); p.getName() 获取类所在包
java
Class clazz = Class.forName("testreflect.Person");//在try中写
//获取类所在的包
Package p = clazz.getPackage();
System.out.println(p.getName());//testreflect
(4)Class sclass = clazz.getSuperclass(); sclass.getName() 获取当前class的父类
java
Class clazz = Class.forName("testreflect.Person");//在try中写
//获取当前class的父类
Class sclass = clazz.getSuperclass();
System.out.println(sclass.getName());//testreflect.Animal
java
public class Person extends Animal{
}
(5)Class sclacc00 = clacc.getSuperclass(); 获取超类(父类)
java
ArrayList<String> list = new ArrayList<>();
//获取集合对应的那个class
Class clacc = ArrayList.class;
System.out.println(clacc);//class java.util.ArrayList
Class sclacc00 = clacc.getSuperclass();
//不为空说明有父类,找父类
while (sclacc != null) {
System.out.println(sclacc00.getName());
sclacc00 = sclacc00.getSuperclass();//注意是sclacc
}
bash
//打印出来的结果
class java.util.ArrayList//这个是clacc
java.util.AbstractList
java.util.AbstractCollection
java.lang.Object
(6)Class[] classes = claxx.getInterfaces(); 获取当前claxx的所有父类接口
java
ArrayList<String> lists = new ArrayList<>();
Class claxx = ArrayList.class;
//获取当前claxx的所有父类接口
Class[] classes = claxx.getInterfaces();
for (Class x:classes) {
System.out.println(x);
}
bash
//结果
interface java.util.List
interface java.util.RandomAccess
interface java.lang.Cloneable
interface java.io.Serializable
(7)Object obj = clatt.newInstance(); 默认调用无参数构造方法创建对象,如果在Person写一个带参数的构造方法会报异常:NoSuchMethodException表示没有找到newInstance此方法
java
Class clatt = Class.forName("testreflect.Person");
//相当于调用Person类中的默认无参数构造方法创建对象
Person pp = (Person) clatt.newInstance();
System.out.println(pp);
(2)Field类相关
1、类中属性的操作
(1)int = getModifers(); Class = getType(); String = getName();
java
Class clazz = Class.forName("testreflect.Person");//在try中写
//通过clazz来获取类中的属性
Field nameField = clazz.getField("name");
//获取属性的修饰符
int modifiers0 = nameField.getModifiers();
System.out.println(modifiers0);//1
//获取属性的类型
Class fclass = nameField.getType();
System.out.println(fclass.getName());
//获取属性的名字
String fname = nameField.getName();
System.out.println(fname);
java
public class Person extends Animal{
public String name;
public int age;
}
2、操作属性,向里面赋值
(1)以前赋值:对象 = new(); 创建对象空间,当前对象空间里有自己的一套元素(属性、方法),然后对象.属性 = 值;
(2)现在是没有创建对象,有类,通过Class类和Field类获得了属性;属性 = 类.getField(); 属性.赋值(哪个对象 , 值);
nameField.set(对象, "要赋的值");
(3)通过clazz来获取类中的属性,但是要保证你从对象中获取的属性值是可以访问的,属性是private就会报异常NoSuchMethodException
第一种:new一个对象给set使用
/
java
/通过一个Class对象来操作Person.class类文件
Class clazz = Class.forName("testreflect.Person");
Person p = new Person();
//通过clazz来获取类中的属性,但是要保证你从对象中获取的属性值是可以访问的,属性是private就会报异常NoSuchMethodException
Field nameField = clazz.getField("name");
//通过属性赋值
nameField.set(p, "曹操");//给Person中的name赋值
java
public class Person extends Animal{
public String name;//如果是私有的就获取不到name属性
public int age;
@Override
public String toString() {
return "{" + name + "," + age + "}";
}
}
java
public class Animal {
public String name;
}
第二种:通过Person p = (Person) clazz.newInstance();
java
//通过一个Class对象来操作Person.class类文件
Class clazz = Class.forName("testreflect.Person");
//newInstance()是个泛型所以给造个型
Person p = (Person) clazz.newInstance();
//通过clazz来获取类中的属性
Field nameField = clazz.getField("name");//这里也是必须是可以访问的才能获取到
//通过属性赋值
nameField.set(p, "曹操");//给Person中的name赋值
3、获取通过属性取值
(1)Field f = getField("属性名"); 知道属性名,属性是共有的通过getField("属性")获取,
Field[] fs = getFields(); 不知道属性名,可以直接都获取getFields();来获取所有属性
如上的两个方法只能获取共有的属性,包含继承过来的父类属性
java
Class clazz = Class.forName("testreflect.Person");
//获取Person中的所有属性,由于Person继承了Animal,所以三个属性
Field[] fields = clazz.getFields();
System.out.println(fields.length);//3
java
public class Person extends Animal{
public String name;//如果是私有的就获取不到name属性
public int age;
}
public class Animal {
public String name;//如果是私有的就获取不到name属性
}
java
try{
//通过一个Class对象来操作Person.class类文件
Class clazz = Class.forName("testreflect.Person");
Person p = (Person) clazz.newInstance();
//通过clazz来获取类中的属性
Field nameField = clazz.getField("name");//保证name属性能访问
//通过属性赋值
nameField.set(p, "曹操");
//通过属性取值, get返回值是个通用型,什么都可以返回,所以造个型
String name = (String) nameField.get(p);
System.out.println(name);//曹操
} catch (Exception e) {
e.printStackTrace();
}
(2)Field f = clazz.getDeclaredField("属性名");
Field[] fs = clazz.getDeclaredFields();
如上的两个方法能获取共有的 和 私有的 属性,但是只能获取本类中的属性,父类的不可以
setAccessable(true); 表示修改属性可以被操作
java
//通过一个Class对象来操作Person.class类文件
Class clazz = Class.forName("testreflect.Person");
Person p = (Person) clazz.newInstance();
//获取类下面为name的属性,私有也能访问
Field f = clazz.getDeclaredField("name");
System.out.println(f.getName());//name
//表示私有属性可以直接被操作
f.setAccessible(true);
f.set(p, "hah");
System.out.println(p);//{hah,0}
String value = (String) f.get(p);
System.out.println(value);//hah
java
public class Person extends Animal{
private String name;//如果是私有的就获取不到name属性
public int age;
@Override
public String toString() {
return "{" + name + "," + age + "}";
}
}
① 反射机制修改String字符串
java
public static void main(String[] args) throws Exception {
String str = new String("abc");
System.out.println(str);
//反射的技术可以获取私有属性,可以操作私有属性,虽然很不合理,
//想要操作属性,必须先获取属性
//1.获取String类对应的那个class
Class clazz = str.getClass();
System.out.println(clazz);//class java.lang.String
//2.通过clazz获取类中的value属性
Field field = clazz.getDeclaredField("value");
//3.直接操作属性的值不可以,所以设置私有属性可以被操作
field.setAccessible(true);
//4.获取value属性里面的值(实际就是内存中地址)
//因为private final char[] value; 虽然可以获取这个value私有属性,但是final是不能拿来赋值的
//直接操作final属性来new个新地址也是不行的,但是你数组中的内容是可以改的value[0],value[..]可以赋值
//所以我们要把这个final char[]数组中的属性值拿出来
//private final char[] value = {'a', 'b', 'c'}String底层char[]是个final修饰的
char[] temp = (char[]) field.get(str);
//5.通过temp的地址引用,找到真实String对象中的属性,修改数组内每一个元素
temp[0] = '吧';
temp[1] = '手';
temp[2] = '是';
//输出str的值
System.out.println(str);//吧手是
}
(3)小总
1、Class类中方法
(1)Object = newInstance(); 获取当前的类的对象(相当于调用类中无参数的构造方法),若类中不存在无参数构造方法,抛出异常NoSuchMethodException
(2)Field = getField("属性名"); 获取类中的属性(公有的,自己类 + 父类)
(3)Field[] = getFields(); 获取类中的全部属性(公有的 自己类 + 父类)
(4)Field = getDeclaredField("属性名"); 获取当前类中的属性(公有 + 私有,自己类)
(5)Field = getDeclaredFields(); 获取当前类中全部的属性(公有 + 私有,自己类)
(6)Method m = class.getMethod("方法名", Class...参数类型); 获取公有的方法(自己类 + 父类)
(7)Method[] m = clazz.getMethods(); 获取所以的方法(公有 自己 + 父类)
(8)Method = getDeclaredMethod("方法名", 参数类型的class...) 获取一个方法(自己类 公有 私有)
(9)Method[] = fetDeclaredMethods(); 获取全部的方法(自己类 公有 私有) 需要setAccessible获取修改权才能操作
获取构造方法
(1)Construcor = clazz.getConstructor(Class..参数类型)
(2)Constructor[] cons = clazz.getConstructors();
(3)clazz.getDeclaredConstructor();
(4)clazz.getDeclaredConstructors();
2、Field类中的常用方法
(1)int = getModifiers(); 获取属性修饰符(权限 + 特征)
(2)Class = getType(); 获取属性的类型对应的那个class
(3)String = getName(); 获取属性的名字
操作属性:
set(对象 ,值);给属性赋值
Object = get(对象); 从某个对象内取得属性的值
如果是私有属性是不能直接操作的
setAccessable(true); 设置一个使用权,准入
三十八、反射机制之方法 和 构造方法
38.1 Method类
1、Menthod类中的常用
(1)int mm = m.getModifiers(); //获取方法的修饰符(权限+特征) (2)Class mrt = m.getReturnType(); //获取返回值数据类型 (3)String mn = m.getName(); //获取方法的名字 (4)Class[] mpts = m.getParameterTypes(); //获取方法参数列表的类型 (5)Class[] mets = m.getExceptionTypes(); //获取方法抛出的异常的类型
java
//获取Person对应的Class
Class clazz = Person.class;
//通过clazz获取其中的方法
//先通过方法名字定位方法,通过方法参数类型对应的Class来找寻
Method m = clazz.getMethod("eat",String.class);
int mm = m.getModifiers(); //获取方法的修饰符(权限+特征)
Class mrt = m.getReturnType(); //获取返回值数据类型
String mn = m.getName(); //获取方法的名字
Class[] mpts = m.getParameterTypes(); //获取方法参数列表的类型
Class[] mets = m.getExceptionTypes(); //获取方法抛出的异常的类型
java
public class Person {
public void eat(){
System.out.println("我是Person中的eat方法");
}
public void eat(String s){
System.out.println("我是Person中带String参数的eat方法");
}
}
(1)如何操作方法
如何操作方法 :就是调用方法,让他执行一次
(1)Object result = invoke(所属的对象,方法执行时需要的参数...); ...的形式
java
public static void main(String[] args) throws Exception {
//获取Person对应的Class
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
//通过clazz获取其中的方法
//先通过方法名字定位方法,通过方法参数类型对应的Class来找寻
Method m = clazz.getMethod("eat",String.class);
String result = (String) m.invoke(p,"测试参数");
System.out.println(result);
}
(2)如果方法是私有的方法,不允许操作,可以设置setAccessable(true) 设置方法使用权;
38.2 Constructor 类中的构造方法
如何操作类中的构造方法?
(1)Constructor c = clazz.getConstructor(Class...参数类型); 获取构造方法
java
//找到person对应的class
Class clazz = Person.class;
//找寻clazz中的无参数构造方法
Constructor con = clazz.getConstructor(String.class);
//执行构造方法
Person p = (Person) con.newInstance("name");
System.out.println(p);
(1)int cc = m.getModifiers(); //获取方法的修饰符(权限+特征) String cn = con.getName(); //获取方法的名字 Class[] cpts = mcongetParameterTypes(); //获取方法参数列表的类型 Class[] cets = mcongetExceptionTypes(); //获取方法抛出的异常的类型
如果操作构造方法 执行一次 创建对象
(1)Object = new Instance(执行构造方法时的所有参数);
(2)con.setAccessible(true);
38.3 设计控制反转IOC
1、反射机制很强大:假如我给定一个String,给完得String字符串可以通过反射机制变成一个类,有了类之后,这个类就可以帮我们创建对象;
我想设计一个方法:给一个字符串(这个字符串代表得是一个类得意思),返回一个对象;对象里面还有属性值;给一个Person.class的字符串,通过这个方法返回一个Person对象
2、还记得我们之前做的考试系统嘛?里面的Question和User类型都是我们自己定义的,两个类的目的不是为了做什么逻辑功能,他们只是为了存储某些值,看似是个对象,更像是个容器;
我们发现这种实体对象没有任何逻辑,那么每一个对象都需要我们自己创建,而且里面的属性还需要我们自己去赋值,是不是太麻烦了?
以前我们创建对象还需要我们自己new。
那我们如果设计一个方法,给一个字符串,他自己就能给我们一个新的对象,而且还把里面的属性给赋值了。多么容易
3、设计一个小工具,替代我们自己创建对象的过程,传递一个字符串,帮我们创建一个对象;这个就是我们以后要学到的Spring开源框架,里面有两个重要思想:IOC控制反转(对象的控制权反转了)、AOP面向切面变成;有了这个思想就方便了,我想创建对象我找别人要就行了,不需要我们自己创建
设计方法,帮我们自己创建对象的工程,传递一个字符串,帮我们创建一个对象,同时还能将对象内的所有属性赋值,这个思想也叫做DI(依赖注入),是将对象的控制权是别人的,别人创建对象的同时,帮我们自动注入属性值
IOC是将控制权反转,把创建对象的控制权给别人
DI是对象的控制权本来就已经是别人的,然后别人创建对象的同时,给这个对象自动注入属性值;DC的前提是有IOC;
java
package ioc;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
public class MySpring {
//设计一个方法,帮我们控制对象的创建,参数String类全名,返回一个对象
//那我们设计这个是不是要保证通用性,那么我们返回类型就不能设定为只返回Person,所以返回Object,然后造型就行了
public Object getBean(String className) {
Object obj = null;
Scanner input = new Scanner(System.in);
System.out.println("请给" + className + "类的对象赋值");
try {
//获取方法传递进来的参数对应的类
Class clazz = Class.forName(className);
//通过Clazz创建一个对象,我们要的不是类,而是类中的对象
//为了做返回值我们把obj定义在try外面
obj = clazz.newInstance();
//在这里做一个自动DI依赖注入,注入的是对象中的所有属性值,怎么赋值?主要是每一个对象属性的类型是不一样的!
//是找到属性直接赋值呢?还是找到属性的对应方法赋值呢?我们能操作私有属性但是非常不合理,构造不好找,所以
//通过set方法找
//找到每一个不同对象中的所有set方法,用这个方法给属性赋值,通常set方法是比较规范的,一般名字是set+属性名
//所以自己可以通过拼接字符串的方式处理名字
//1.通过clazz找寻类中的所有私有属性---》获取每一个属性的名字---》然后拿属性名处理set属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取属性名
String fieldName = field.getName();
//2.手动的拼串,拼接属性对应的set方法名,setTitle setAnswer
String firstLetter = fieldName.substring(0,1).toUpperCase();//属性的首字母大写
String otherLetters = fieldName.substring(1);//从第一个位置截取到最后,属性除了首字母之外的其他字母
//拼接用StringBuilder拼接效率高
StringBuilder setMethodName = new StringBuilder("set");
setMethodName.append(firstLetter);
setMethodName.append(otherLetters);
//3.获取field对应的属性类型----找寻set方法时候传递参数用
Class fieldClass = field.getType();
//4.通过处理好的set方法名,寻找类中的set方法
Method setMethod = clazz.getMethod(setMethodName.toString(),fieldClass);//fieldClass每一个属性的类型
//5.找到的setMethod一执行,属性就立马,赋值成功
System.out.println("请给" + fieldName + "属性提供值");
String value = input.nextLine();
//值的类型应该是属性得类型,需要执行属性对应得set方法,给属性赋值,方法就结束啦
//属性的值现在接收过来(Scanner 文件内读取) 全都是String,有个问题?
//执行set方法的时候,方法需要的值不一定都是String类型,有可能是Integer、Float等...
//所以要的值应该是属性类型的值
//如何将所有的String类型的值---->转化为属性类型的值
//Integer包装类,八个包装类有七个都含有带String的构造方法 new Integer(String) new Float(String)...
//只有Char没有
//可以利用包装类带String的构造方法处理,我们要找的是属性类对应的带String参数的构造方法
Constructor con = fieldClass.getConstructor(String.class);
//构造方法把我们的value构造成了String对应的对象
Object result = con.newInstance(value);//这个最后的结果,就是我们想要的
setMethod.invoke(obj, result);
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
java
package ioc;
public class Question {
private String title;//题干
private String answer;//答案
public Question(){}
public Question(String title, String answer) {
this.title = title;
this.answer = answer;
}
@Override
public String toString() {
return "{" + title + ", " + answer + "}";
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
}
java
package ioc;
public class Person {//实体对象
private String name;
// private int age;//最好写Integer,写int的包装类,因为安全
private Integer age;
private String sex;
public Person() {}
public Person(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
三十九、注解(初始 + 元注解)
39.1 注解Annotation
1、注解的写法 @xxx
2、注解可以放在哪里?
(1)类的上面、属性上面、方法上面、构造方法上面、参数前面
3、注解的作用
(1)用来充当注释的作用(仅仅是一个文字的说明) @Deprecated
(2)用来做代码的检测 或 验证 @Override
(3)可以携带一些信息 或 内容,(像数组、集合都是携带信息之后供我们使用,但是都是内存中的,所以后来有了文件,是永久存储在硬盘上的),实际上注解也不例外 @
4、java中有一些人家写号的注解供我们使用
(1)@Deprecated 用来说明方法是废弃的
(2)@Override 用来做代码检测的,检测此方法是否是一个重写的方法
(3)@SuppressWarnings(信息) 这个信息是个String[]的数组,所以可以传递一个大括号进去如:{" " , " "} ,如果数组内的元素只有一个长度如:@SuppressWarnings(就写一个信息),这时就可以省略{},然后写出@SuppressWarnings("")的形式
**注:**尽量不要添加SuppressWarnings,而是通过重写一些代码去掉这些,除了其中的unchecked都不要去添加
①unused:变量定义后未被使用,因为在IDEA中str下面有个小黄波浪线(警告),有些公司要求不让写的代码出警告,所以可以添加此注解
java
//unused表示变量未使用过,如:str是为使用过的,所以这里可以加个注解
@SuppressWarnings("unused")
String str = "asd";//这样str下就没有黄色波浪线了
②serial:类实现了序列化接口,不添加序列化ID号
java
@SuppressWarnings("serial")
public class Person implements Serializable { //这样Person下就没有黄色波浪线了
private static final long serialVersionID = 1L;
}
③rawtypes:集合没有定义泛型
java
@SuppressWarnings("rawtypes")
ArrayList list = new ArrayList();//这样就不会出警告了
④deprecation 方法以废弃
⑤unchecked 选择不检测
java
@SuppressWarnings({"deprecation","ununused"})
Date d = new Date(2024,11,15);//第一个是Date是过期的,所以我们选择不检测deprecation,第二个是d变量没有使用ununused
5、注解中可以携带信息,可以不携带;信息不能随意写,信息的类型只能是如下的类型
(1)基本数据类型
(2)String类型
(3)枚举类型enum
(4)注解类型@
(5)数组类型[ ] 数组内部需要是如上的四种类型
6、如何自己描述一个注解类型?你说是注解就是嘛?所以?
(1)通过@interface来定义一个新的注解类型
java
public @interface MyAnnotation {
}
(2)发现写法与接口非常相似(可以利用接口的特点来记忆注解)
①可以描述public abstract 的方法,注解方法必须有返回值,返回的类型是我们总结的那5种;所以通常我们在注解中写方法不写这些修饰符和特征修饰符,而是直接写返回类型和方法名,如下:
java
String test();
(3)我们自己定义的注解,如果想要拿来使用,光定义还不够,还需要做很多细致的说明(需要利用Java提供好的注解来说明),我们也成为元注解(也是注解,不是拿来使用的,是用来说明注解的)
7、元注解:
(1)@Target:描述当前的这个注解可以放置在哪里写的
java
package myself;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
//就是说咱们自己定义的注解可以放在,属性FIELD、方法METHOD、构造方法CONSTRUCTOR的上面,其中FIELD、METHOD、CONSTRUCTOR是用枚举类型写的
//如果还想让咱们的注解还能放在其他地方,就可以继续添加相关的枚举类型
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface MyAnnotation {
String test();
}
还可以静态导入
java
package myself;
import java.lang.annotation.Target;
//可以看出下面多加了一个static
import java.lang.annotation.ElementType;
import static java.lang.annotation.ElementType.*;
@Target({FIELD,METHOD, CONSTRUCTOR})
public @interface MyAnnotation {
String test();
}
(2)@Retention:描述当前的这个注解可以存在什么作用域中的
(3)@Inherited:描述当前这个注解是否能被子类对象继承,如果加了就可以继承
(4)@Document:描述这个注解是否能被文档所记录(不常用)
8、使用自己描述的注解
(1)在注解里面描述了一个方法,方法没有参数,方法是有返回值String[ ];使用注解的时候,让我们传递参数?
(2)理解为注解的方法做事,将我们给他的信息传递给别人
相当于我们把abc这个字交给了test这个方法,然后这个test方法得到这个abc,是将这个abc信息传递给别人的
(3)使用别人写好的注解好像不用写方法名?我们自己定义的方法必须写名字才能传递为什么?
如果我们自己定义的注解中只有一个方法,并且这个方法名字叫value,那么在使用的时候,就可以省略不写了;相当于上面讲的。如果传递的信息是一个数组,数组内只有一个元素,就可以省略了
java
public class Person {
//其实不是不写而是省略了,相当于@MyAnnotation(value="abc")
//所以我们在定义的时候尽量让方法名字为value
@MyAnnotation("abc")
private String a;
}
java
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Target({FIELD,METHOD, CONSTRUCTOR})
public @interface MyAnnotation {
String value();
}
(4)如果自己定义的注解中有两个方法,在使用的时候就要加上方法名
java
public @interface MyAnnotation {
String value();
int test();
}
java
public class Person {
@MyAnnotation(value = "abc", test = 10)
private String a;
}
谁前谁后无所谓的。
39.2 注解(反射注解 + 注解应用)
1、如何解析注解内携带的信息(注解应用场景)
(1)获取属性上的注解信息
java
package testMyannotation;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.*;
@Target({CONSTRUCTOR, METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String[] value();//默认方法--有人也叫做属性
}
java
package testMyannotation;
public class Persons {
//我想用反正的机制,来解析我传递过去的字
@MyAnnotation("哈哈")
private String name;
}
java
package testMyannotation;
import java.lang.reflect.Field;
public class TestMain {
public static void main(String[] args) throws Exception {
//解析Person类中,属性上面的注解信息---反射技术
//1.获取Person对应的类Class
Class clazz = Persons.class;
//2.通过clazz获取里面的属性
Field field = clazz.getDeclaredField("name");
//3.通过field来获取属性上面的注解对象, 传递的是一个class
// Annotation a = field.getAnnotation(MyAnnotation.class);//你会发现是个多态,你前面的类型大于后面的类型,通过a是调用不到的
MyAnnotation a = (MyAnnotation) field.getAnnotation(MyAnnotation.class);
//4.注解得到了,利用a对象执行以下value方法,帮我们搬运过来
String[] values = a.value();
System.out.println(values[0]);
}
}
(2)你会发现前三步都是利用反射技术来完成的,但是第四步是利用普通的调用对象的实现完成的,我们只用反射
java
package testMyannotation;
public class Persons {
//我想用反正的机制,来解析我传递过去的字
@MyAnnotation({"哈哈","16","sda"})
private String name;
}
java
package testMyannotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TestMain {
public static void main(String[] args) throws Exception {
//解析Person类中,属性上面的注解信息---反射技术
//1.获取Person对应的类Class
Class clazz = Persons.class;
//2.通过clazz获取里面的属性
Field field = clazz.getDeclaredField("name");
//3.通过field来获取属性上面的注解对象, 传递的是一个class
Annotation a = field.getAnnotation(MyAnnotation.class);
//我想要a对象里面的value方法,所以要让a执行一遍
//a既然是对象,我要的不是a对象. ;我想要的是a//对象对应的类型,它里面有一个方法,让这个方法去执行
//4.利用反射执行a中的value方法,帮我们把信息搬运过来
Class aclazz = a.getClass();//a对象获取它对应的class,通过对象去找类去,用类去操作它;aclazz是a注解对应的那个类
//5.通过aclazz获取里面的value方法
Method amethod = aclazz.getMethod("value");
//6.执行value的方法,获取传递的信息
//amethod.invoke(所属的对象,方法执行时需要的参数);invoke是注解的方法
String[] values = (String[]) amethod.invoke(a);
//7.展示以下values内的信息,也就是注解传递给value的信息
for (String v:values) {
System.out.println(v);// 哈哈 16 sda
}
}
}
(3)上面是获取Person属性上面的注解信息,这次我们获取Person中方法的注解信息,也是通过反射
java
public class Persons {
@MyAnnotation({"喜喜", "23", "女"})
public void eat(){}
}
java
public static void main(String[] args) throws Exception{
//获取Persons类中,方法上面的注解信息,通过反射机制
Class clazz = Class.forName("testMyannotation.Persons");
Method method = clazz.getMethod("eat");
Annotation a = method.getAnnotation(MyAnnotation.class);
Class aclazz = a.getClass();
Method amethod = aclazz.getMethod("value");
String[] values = (String[]) amethod.invoke(a);
for (String v:values) {
System.out.println(v);//喜喜 23 女
}
}
(1)什么时候用注解
1、利用反射技术实现了一个IOC,就是对象由别人来创建,创建的同时自动注入属性信息,自动注入属性的信息,这个信息是利用Scanner输入进来的,输入进来的实际是个String的字符串,那么没有Scanner,我们可以存入文件里,那我们还可以将String字符串携带人注解内,产生一个注解,携带这些信息,所以我们可不可用注解做一个IOC的管理?
java
package myselfannotation;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.*;
@Target({CONSTRUCTOR, METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
public abstract String[] value();//默认方法--有人也叫做属性public abstract可以不写
}
java
package myselfannotation;
public class Persons {
private String name;
private Integer age;
private String sex;
@MyAnnotation({"曹宇希","18","男"})
public Persons() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
java
package myselfannotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MySpring {
//方法,给一个类名字,返回一个对象,对象内的属性值存在着
public Object getBean(String className) {
//想通过className创建对象,首先是有个对象
Object obj = null;//先拿这个变量来接收最终的对象,然后返回它
try{
//1.通过传递的className来获取对应的类Class,有了类以后才能反射
Class clazz = Class.forName(className);//forName要求参数是一个类全名(包名加类名)
//2.通过clazz创建一个空对象,因为对象里面是有属性的,但是通过反射的技术创建的这个属性是没有值的,所以我们认为是空的对象
//这里可通过obj = clazz.newInstance()来创建,但是前提是你找寻的类必须有无参的构造方法
//也可以通过下面这种方法找寻构造方法,下面这种是找寻无参的构造器,要是想找寻有参的就给getConstructor加参数
Constructor con = clazz.getConstructor();
obj = con.newInstance();
//3.创建对象以后,将对象内的所有属性自动的赋值,这个赋值的过程我们叫做DI(依赖注入)
//我们会把这个值存入到两个地方1.存入文件里2.存入注解里携带过去
//值存文件里好处:代码包装起来不能修改,但是文件是可以进行修改的 不好:在开发的时候,源代码和配置文件不在一起,读取/修改比较麻烦
//值存注解里好处:开发编写时方便,源代码和注解在一起,读取调整比较容易 不好:代码包装起来后,注解内携带的信息是不能修改的
//4.首先获取属性的值,是在当前类的无参数构造方法之上的那个注解
//想找构造方法,先找类,这个类就是clazz,然后再找构造方法,这个构造方法就是con
Annotation a = con.getAnnotation(MyAnnotation.class);
//5.获取a注解对象内携带的信息,这个信息是我们当前Person对象所有的属性值
//用反射,先获取a对象的类型,是注解类型对应的那个类,是Person类对应的注解的那个类
Class aclazz = a.getClass();
Method amethod = aclazz.getMethod("value");//获取注解里面的方法value
//方法获取完了之后,去执行就可以了
String[] values = (String[]) amethod.invoke(a);
System.out.println("注解内获取的属性值" + values.length);//3 注解内获取的属性值
//6.将values中的每一个值 对应的赋值赋给属性
//想赋值,先找寻属性对应的set方法赋值 getFields只能得到公有属性public
Field[] fields = clazz.getDeclaredFields();//获取全部的私有属性
System.out.println("从类中获取的全部属性" + fields.length);
for (int i = 0; i < fields.length; i++) {
//找寻属性的名字
String fieldName = fields[i].getName();
//属性名有了之后,我们要处理一下set方法,将属性名字的第一个字母大写,因为set方法就是首字母大写
String firstLetter = fieldName.substring(0,1).toUpperCase();//第一个首字母
String otherLetter = fieldName.substring(1);//其他字母
//然后我们要把字符串在拼接上
StringBuilder setMethodName = new StringBuilder("set");
setMethodName.append(firstLetter);
setMethodName.append(otherLetter);//最后得到的串,是属性对应的set方法名
//通过处理好的set方法名字找到对应的set方法
//找方法通过反射来找,setMethodName这个类的方法是clazz的这个类,也就是Person
//由于拼接的时候是StringBuilder,所有还要将它变成String
// clazz.getMethod(setMethodName.toString(), 属性类型); set方法的参数是属性类型 属性就是field
Method setMethod = clazz.getMethod(setMethodName.toString(), fields[i].getType());
//执行找到的set方法,给对应的属性赋值
//invoke这个执行方法需要对象,谁来执行set方法呢?应该是Person对象来执行,那么执行方法是需要条件的Person.setName()
// ,它需要一个你想设定的值,这个值就是你注解里面的,注解里面的值是每一个值对应一个set方法,所以还需要一个索引号
//写一个正常for循环是最好的
// setMethod.invoke(obj, values[i]);
//但是values有个不好的地方,这个values无论是从文件里还是从注解里,读取出来的时候都是String类型的,但是setMethod在执行方法
//的时候,它的属性值不一定都是String类型的,所以需要将注解内读取到的String类型的值,转化成属性类型对应的类型值
Class fieldType = fields[i].getType();//我们可以利用属性类型的构造方法
//先找到属性的类型fieldType,属性的类型里面有一个带String类型的构造方法,fieldType.getConstructor(String.class)
//这个写完后我们就找到了属性类型的构造方法,然后利用构造方法去创建对象,创建出来的是属性类型的对象,属性对象你可以把values
//给它,values[i]是属性的一个值,它会把这个字符串类型的值通过构造方法,给你构建成一个属性类型的对象
setMethod.invoke(obj, fieldType.getConstructor(String.class).newInstance(values[i]));
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
java
package myselfannotation;
public class TestMain {
public static void main(String[] args) {
/*
获取一个Person类型的对象
以前第一种
Persons p = new Persons();
p.setName("曹宇希");
p.setAge(18);
p.getSex("男");
以前第二种
Persons p = new Persons("曹宇希",18,"男");
*/
//获取一个Person对象,不要自己处理,跟MySpring
//现在对象的创建权利反转了(IOC) 赋值(自动DI依赖注入) 别人处理
MySpring ms = new MySpring();//相当于是一个管理者,可以帮我们创建对象,然后自动赋值
Persons ps = (Persons) ms.getBean("myselfannotation.Persons");//由于通用性我们的getBean是Object类型的
System.out.println(ps.getName() + "----" + ps.getAge() + "----" + ps.getSex());//最后得到的应该是我们注解里面传递的信息
}
}