Java基础课的中下基础课06

目录

三十四、线程基础

[34.1 理解线程](#34.1 理解线程)

[34.2 线程的几种状态](#34.2 线程的几种状态)

[34.3 实现线程的过程](#34.3 实现线程的过程)

(1)实现线程第一种方法

(2)实现线程第二种方法

[34.4 模拟火车站售票](#34.4 模拟火车站售票)

三十五、线程之生产消费者模型

[35.1 生产消费者模型](#35.1 生产消费者模型)

(1)线程的资源抢夺问题

(2)解决资源抢夺synchronized和不加安全锁时产生的异常

(3)生产消费者模型总结

三十六、join方法和死锁

[36.1 join方法](#36.1 join方法)

(1)设计模型让two线程加入到One线程

(2)模型Two线程加入One线程,Three锁Two线程

[36.2 死锁](#36.2 死锁)

(1)模拟死锁哲学家就餐问题

(2)解决死锁的问题

[36.3 线程应用---定时器](#36.3 线程应用---定时器)

(1)模拟垃圾短信

三十七、反射机制

[37.1 反射技术reflect​编辑](#37.1 反射技术reflect编辑)

(1)Class类相关

(2)Field类相关

[① 反射机制修改String字符串](#① 反射机制修改String字符串)

(3)小总

[三十八、反射机制之方法 和 构造方法](#三十八、反射机制之方法 和 构造方法)

[38.1 Method类](#38.1 Method类)

1、Menthod类中的常用

(1)如何操作方法

[38.2 Constructor 类中的构造方法](#38.2 Constructor 类中的构造方法)

[38.3 设计控制反转IOC](#38.3 设计控制反转IOC)

[三十九、注解(初始 + 元注解)](#三十九、注解(初始 + 元注解))

[39.1 注解Annotation](#39.1 注解Annotation)

[39.2 注解(反射注解 + 注解应用)](#39.2 注解(反射注解 + 注解应用))

(1)什么时候用注解


三十四、线程基础

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());//最后得到的应该是我们注解里面传递的信息

    }
}
相关推荐
云飞云共享云桌面1 分钟前
东莞精密机械制造工厂如何10个SolidWorks共用一台服务器资源
java·运维·服务器·网络·数据库·电脑·制造
小此方2 分钟前
C语言自定义变量类型结构体理论:从初见到精通(上)
c语言·开发语言
毕设源码-赖学姐7 分钟前
【开题答辩全过程】以 网络药店管理系统为例,包含答辩的问题和答案
java·eclipse
努力也学不会java8 分钟前
【Java并发】揭秘Lock体系 -- 深入理解ReentrantReadWriteLock
java·开发语言·python·机器学习
埃泽漫笔20 分钟前
消息队列延迟与过期问题的实战解决
java·mq
vxtkjzxt88821 分钟前
自动化脚本矩阵运营
开发语言·php
王严培.30 分钟前
7.MATLAB疑难问题诊疗的技术
开发语言·matlab·信息可视化
花花无缺39 分钟前
资源泄露问题
java·后端·http
wjs202442 分钟前
PHP MySQL 使用 ORDER BY 排序查询
开发语言
爱敲代码的TOM1 小时前
深入剖析Java通信架构下的三种IO模式2
java·开发语言·架构