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

    }
}
相关推荐
忒可君29 分钟前
C# winform 报错:类型“System.Int32”的对象无法转换为类型“System.Int16”。
java·开发语言
GuYue.bing40 分钟前
网络下载ts流媒体
开发语言·python
斌斌_____1 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
StringerChen1 小时前
Qt ui提升窗口的头文件找不到
开发语言·qt
路在脚下@1 小时前
Spring如何处理循环依赖
java·后端·spring
数据小爬虫@1 小时前
如何利用PHP爬虫获取速卖通(AliExpress)商品评论
开发语言·爬虫·php
撸码到无法自拔1 小时前
深入理解.NET内存回收机制
jvm·.net
一个不秃头的 程序员1 小时前
代码加入SFTP JAVA ---(小白篇3)
java·python·github
丁总学Java1 小时前
--spring.profiles.active=prod
java·spring
上等猿2 小时前
集合stream
java