自学javase回顾(10/10)
1、多线程
2、反射机制
3、注解机制
多线程
一、多线程简介(未更新完)
多线程大佬详解
1、什么是多线程?(关键字:Thread)
①多线程:一个进程序同时运行很多子任务叫做多线程。
②进程:一个进程对应一个应用程序。例如:在 windows 操作系统启动 Word 就表示启动了一个进程。
③线程:一个程序能够同时做很多子任务方法,每一个子任务就是一个线程。
【1】多线程:一个软件进程序同时运行很多子任务叫做多线程.
在王者荣耀游戏这一个进程中,有着一边买装备一边清兵多个线程同时并发进行着.(再例如jvm启动的时候,jvm就是一个进程,main方法主线程和GC回收方法就是两个线程并发执行。)
【2】多进程:现代的单双核计算机都是支持多进程的,即在同一个操作系统中,可以启动多个进程软件。
①只是对于多核计算机来说,一边玩游戏(游戏进程)一边听音乐(音乐进程)是可以同时运行的。
②而对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程不是同时在运行的(单核计算机的 CPU 只能在某个时间点上做一件事)
只是由于计算机将在“游戏进程”和“音乐进程”之间频繁的切换执行,切换速度极高极快,人类产生错觉感觉游戏和音乐在同时进行。
2、线程和进程关系的注意点:
(进程 (公司)> 线程(各个部门) >方法(各个员工))
①线程是进程的一个执行单元,一个进程可以启动多个线程。 一个线程可以有多个方法压栈。(进程看成一个公司,线程看成一个公司的各个部门,方法看成是部分里面的各个员工)
②进程A与进程B之间,内存互相独立不共享。
(比如阿里APP和京东APP一点关系都没,资源一点都不会共享)
③但线程A与线程B之间(使用多线程机制后),共享堆和方法区内存,不共享栈内存。(即多少个线程多少个栈,多个线程共享一个堆内存和方法区)
(线程和线程之间互相独立,互不干扰,即一个线程结束或者出问题不会影响别的线程运行)
④总结:因此使用多线程机制后,main方法主线程结束程序也不一定结束了,main方法结束只是主线程(主栈)结束了而已,其他子线程(分支栈)还可以继续执行。
3、多线程并发作用:
①多线程不是为了提高某个线程的执行速度,而是多个线程一起并发然后提高整个应用进程(程序APP)的使用效率。
(即对于某个线程该执行多久还是多久执行速度不会变,但是可以同时执行多个线程就宏观来说提高了整个进程程序的复用效率)
②使用多线程后,有主栈和分支栈,即main主栈结束程序不会结束,分支栈都还能和执行
4、怎么分析一个进程有几个线程?
①只要没有实现多线程机制,那么任何java程序就只有一个主线程,即主栈main方法结束整个程序结束。
②实现多线程后,就是看有几个栈内存空间即可,因为线程之间栈内存不共享的。
③千万不要把一个方法看成一个线程(低级错误)
5、多线程输出特点(主线程和分支线程的输出特点)?
总特点:有先有后,有多有少
1、每次执行顺序和运行结果都不同,因为每次线程抢夺的时间片可能不同。
2、即主线程和t1t2分支线程会抢时间(不规则输出,但是主和分支,各自互不干扰,各司其职)
6、线程的生命周期?(大致了解)
①创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
②就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
③运行(running)状态: 执行run()方法
④阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
⑤死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)
7、分析顺序执行VS多线程并发VS多线程并行:
假设一个案例:
【1】主线程main,吃饭分支线程,喝酒t2分支线程(一个主类main开始,二个分支类run开始,一起去抢去夺CPU时间片依次交替执行,不必顾虑各自是否结束或者中断,但是还是得遵守自上而下的顺序运行程序)
【2】
本示例主要启动3个线程,一个主线程main thread、一个吃饭线程(Thread-0)和一个喝酒线程(Thread-1),共三个线程, 三个线程并发切换着执行。
main线程很快执行完,吃饭线程和喝酒线程会继续执行,直到所有线程(非守护线程)执行完毕,整个程序才会结束,main线程结束并不意味着整个程序结束。
【3】顺序编程先吃饭后喝酒要花10秒,而并发编程一边吃饭一边喝酒总共用时5秒,并发比顺序编程更快,因为并发编程可以同时运行,而不必等前面的代码运行完之后才允许后面的代码
① 顺序:必须先吃饭方法吃完了结束了,才能开始去开始喝酒方法。
②并发:就是一边吃饭一边喝酒(虽然一起开始,但还是有先后顺序,依次交替干吃,因为要抢夺cpu时间。
嘴巴可以先喝一口酒再吃一口饭,也可以先吃一口饭再喝一口酒)。(但不能同时吃饭喝酒,因为只能用嘴巴这个进程)
③并行:一边吃饭一边看电影,可以同一个时间节点,去实现吃饭和看电影。(因为一个用嘴巴进程一个用眼睛进程,多核CPU)
顺序:不能一起执行,必须要等t1执行完,t2才能开始。
并发:t1和t2一起各自执行,但要同一个时间段依次交替去执行。(因为单核CPU,t1t2要抢夺CPU时间片)
并行,不仅t1和t2一起各自执行,还可以同一个时间节点并肩执行。(因为多核CPU,t1t2不用抢时间,各自用各自CPU)
1. 顺序编程描述
顺序编程:程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。
public class Main {
// 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚才能喝酒
public static void main(String[] args) throws Exception {
// 顺序执行,先吃饭再喝酒
eat();
drink();
}
private static void eat() throws Exception {
System.out.println("开始吃饭?...\t" + new Date());
Thread.sleep(5000); //这里休眠五秒是不会让位给别的线程的。
System.out.println("五秒后结束吃饭?...\t" + new Date());
}
private static void drink() throws Exception {
System.out.println("开始喝酒?️...\t" + new Date());
Thread.sleep(5000);
System.out.println("五秒后结束喝酒?...\t" + new Date());
}
}
2. 并发编程详细描述
并发编程:多个任务可以同时做,常用与任务之间比较独立,互不影响。
线程上下文切换:
同一个时刻一个CPU只能做一件事情,即同一时刻只能一个线程中的部分代码,
假如有两个线程,Thread-0和Thread-1,
刚开始CPU说Thread-0你先执行,给你3毫秒时间,Thread-0执行了3毫秒时间,
但是没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,当时的变量的值是多少,
然后CPU说Thread-1你可以执行了,给你2毫秒的时间,Thread-1执行了2毫秒也没执行完,
此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,当时的变量的值是多少,
此时CPU又说Thread-0又该你,这次我给你5毫秒时间,去执行吧,
此时CPU就找出上次Thread-0线程执行到哪行代码了,当时的变量值是多少,
然后接着上次继续执行,结果用了5毫秒就Thread-0就执行完了,就终止了,
然后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,当时的变量值是多少,
然后接着上次继续开始执行,结果Thread-1在4毫秒内也执行结束了,
Thread-1也结束了终止了。CPU在来回改变线程的执行机会称之为线程上下文切换。
public class Main {
public static void main(String[] args) {
// 一边吃饭一边喝酒
new EatThread().start();
new DrinkThread().start();
}
}
class EatThread extends Thread{
@Override
public void run() {
System.out.println("开始吃饭?...\t" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束吃饭?...\t" + new Date());
}
}
class DrinkThread extends Thread {
@Override
public void run() {
System.out.println("开始喝酒?️...\t" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束喝酒?...\t" + new Date());
}
}
总结:
1、并发(Concurrent):指多个事件在同一时间点一起出发叫并发,
但是却可以依次交替做不同事,而多线程是并发的。
例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会并发交替执行),
用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
2、并行(Parallel):指多个事件在同一时间点发生并且同时一起做,即可以同时做不同事。
例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户主线程仍然处于等待状态。
二、多线程的实现方式?
实现多线程的三种方法:
1、第一种:直接继承Thread类(不推荐)。编写一个自定义类MyThread去继承Thread类。重写run()方法。(无返回结果)
2、第二种:实现Runnable接口(推荐)。编写一个自定义类MyThread实现承Runnable接口。重写run()方法。 + 匿名内部类(无需拿着类实现接口,直接拿接口充当接口方法,实现匿名内部类)
3、第三种:实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
优劣:
①Thread: 继承类, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
②Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
(面向接口可随意更换接口,可插拔功能强大)
③Callable:实现接口,call方法别run优点是有返回值有返回结果。
④当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现
1、详解第一种:
自定义类直接继承Thread类。
第一步: 编写一个自定义类MyThread去继承Thread类(即这个类充当了分支线程。
必须重写好run方法,作用相当于main方法,用于压分支栈的栈底部即最先进入和执行)
第二步: new MyThread自定义类对象, 去调用start方法,start方法是开辟了新的栈空间,启动分支线程。(主线程和分支线程会一起形成多线程,并发先后依次交替执行,多人运动)
(PS:新分支栈空间的run方法和主栈main方法是平级的)
package com.bjpowernode.javaSE.多线程机制.多线程简介和实现;
public class 实现多线程第一种方式 {
public static void main(String[] args) {
//这里属于main方法,是主线程,在主栈中运行。
//第一步: 编写一个自定义类MyThread去继承Thread类(重写好分支线程的run方法,作用相当于主线程的main方法)
//第二步;new MyThread对象
Thread t =new MyThread();
// t.run(); 注意直接调run方法是不会开辟分支栈空间和启动分支线程,就变成单线程方法一直在main主线程中压栈和执行。(就不是多线程了)
t.start();//前提自定义类MyThread 继承了Thread的start方法,
//第三步:必须先去调用start方法:为了开辟一个新的栈空间,启动分支线程。(然后瞬间就会结束start方法)
//分支线程启动后立马结束start方法,然后立即会自动调用和进入分支栈的run方法(处于分支栈的栈底部,即最先进入和执行里面的代码)。
//run方法在分支栈的栈底部,main方法主栈的栈底部。两者地位一样平级,都是最先压栈。
//总结:start方法作用:开辟新的栈空间,启动分支线程,
// run方法和main方法作用一样,用于最先进入该方法和压栈。
//下面的for代码依然还是在主线程中运行。
for(int i=0;i<1000;i++){
System.out.println("主线程main----->"+ i );//运行结果每次都不同,因为每次线程抢夺的时间片可能不同
}
}
}
/*
结果:每次都不一样的,因为每次线程抢夺的时间片可能不同
....
主线程main----->289
分支线程---->0
分支线程---->1
主线程main----->290
主线程main----->291
...........
*/
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段run方法程序运行在分支线程中.
for(int i=0;i<1000;i++){
System.out.println("分支线程---->" + i);
}
}
}
2、第二种方式详解:
第一步:自定义MyThread类实现implements Runnable接口
第二步:把Thread线程对象new出来,然后放自定义分支线程类(此时实现了runnable变为可运行的自定义类对象),再放入线程的构造方法。
作用:把Runnable可运行接口对象封装成为Thread线程对象的一个实例化类属性,然后给线程类插入可多线程的功能
第三步:启动start方法开辟栈空间,run压栈方法开始运行
PS:Thread代替了MyThread类启动了多线程
即Thread线程类通过Runnable接口属性间接进入实现类MyThread重写好的run方法去了(多态).
package com.bjpowernode.javaSE.多线程机制.多线程简介和实现;
public class 实现多线程第二种方式 {
public static void main(String[] args) {
Runnable t = new MyThread1(); // t是为了给构造方法赋值, Thread has a Runnable 变为可运行的
Thread thread = new Thread(t);
thread.start();
for(int i=0;i<3;i++){
System.out.println(i+"main");//0my 0main 1my 1main 2my 2main
}
}
}
class MyThread1 implements Runnable{ //Runnable接口只有一个run方法
public void run(){
for(int i=0;i<3;i++){
System.out.println(i+"my");
}
}
}
/*
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}).start();
// 尾部代码块, 是对匿名内部类形式的语法糖
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}.start();
// Runnable是函数式接口,所以可以使用Lamda表达式形式
Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());};
new Thread(runnable).start();
第三种方式详解:
1、:MyCallable实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
【1】Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕。
【2】Callable 也是一种函数式接口
【3】call()方法:相当于run方法但是有返回值。即返回结果。(使用futureTask.get()方法获取,)
【4】FutureTask 可以泛型的类对象,
2、具体实现第三种多线程的流程:MyCallable实现Callable接口------>包装成FutureTask------>再包装成Thread----->启动线程开始
MyCallable callable = new MyCallable(); //创建自定义接口对象,实现了Callable接口
FutureTask<Integer> futureTask = new FutureTask<>(callable); // Callable接口包装成FutureTask的一个实例化属性,即放入FutureTask构造方法
Thread thread = new Thread(futureTask);//再把FutureTask抽象类包装成Thread的一个实例化属性,即放入Thread构造方法
thread .start(); //启动线程
3、这种第三种方法的优点缺点????
优点:可以在别的线程中调用Callable线程get方法,拿到Callable线程call方法的执行结果。(而前面两种的run方法无执行结果的void)
缺点:但同时调用 get方法,也会阻塞“调用位置”所处的当前线程(这里为main),分支线程执行结果完,main才能继续输出。
即变为顺序排队执行了,程序效率变低,不是并发一起输出了。
5、Callable接口底层源代码:
/*@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
// 构造函数
public FutureTask(Callable<V> callable);
// 取消线程
public boolean cancel(boolean mayInterruptIfRunning);
// 判断线程
public boolean isDone();
// 获取线程执行结果
public V get() throws InterruptedException, ExecutionException;
}
RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
*/
package com.bjpowernode.javaSE.多线程机制.多线程简介和实现;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class 实现多线程第三种方式 {
public static void main(String[] args) throws Exception {
// 将Callable包装成FutureTask,FutureTask也是一种Runnable
MyCallable callable = new MyCallable(); //创建自定义接口对象,实现了Callable接口
FutureTask<Integer> futureTask = new FutureTask<>(callable); // Callable接口包装成FutureTask的一个实例化属性,即放入FutureTask构造方法
Thread thread = new Thread(futureTask);//再把FutureTask抽象类包装成Thread的一个实例化属性,即放入Thread构造方法
thread .start(); //启动线程
Thread.currentThread().setName("main主线程");
thread.setName("t1");
//调用 get方法会阻塞“调用位置”所处的线程(这里为main)
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum); //main方法的线程名字+id+分支线程返回结果
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
return sum;
}
}
三、Thread线程的常用方法汇总:
Thread类也是sun公司已经写好的,我们可以直接调用即可。
线程的常用方法:
1、start(): 启动一个新分支线程,开辟栈空间,线程之间是没有顺序的,各自执行。是按CPU分配的时间片优先级来回切换的。
【1】 分支线程的属性设置必须要在start()方法之前写好,不然启动之后不算数了。
【2】面试题:启动线程的是start不是run。(run只是相当于main用来压栈的一个调用方法而已)。
2、run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,
【1】run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。
有些面试官经常问一些启动一个线程是用start()方法还是run()方法,为了面试而面试
【2】子类MyThread中的共有的run()方法不能抛出异常,只能try…catch,因为父类Thread类中的run方法没有抛出任何异常,因此子类抛出异常不能比父类多。
除非是子类MyThread中特有的方法就可以抛出异常,因为父类没有该方法。
特殊:Thread.currentThread():
静态方法获取当前线程对象(可以为主线程可以为分支线程) : 这个方法对象不固定看出现在哪,相当于this,指向当前线程类对象引用。
(直接输出这个对象是Thread[main,5,main]乱码,一般和getName联用,直接输出对象名字)
//如果当前线程对象类出现在主线程的main方法中,那么当前线程对象为main主线程。
//如果当前线程对象类出现在分支线程的run方法中,那么当前线程对象为t1/t2分支线程。
//如果如果当前线程对象类出现在一个任意类的方法中,那么就要看这个类对象被谁调用,在main方法调用就是main主线程。
(具体看t1和t2谁先抢占到CPU时间片去调用run方法,当前分支线程currentThread对象就是谁)
1:Thread.currentThread().getName()、setName() :静态方法修改和设置当前线程对象名字。 = this.getName() (一般用于main线程)
2、t.getName()、setName():动态方法获取分支线程名字、设置线程名字(默认为Thread-0,Thread-1,Thread-2。。。。。) (一般用于t1线程对象)
3、Thread.sleep():使当前线程休眠,单位毫秒.(1000*5 即编译后也要5秒才会运行出来结果,注意是静态方法,在哪调sleep哪个线程会进入睡眠,不看引用看类名)
4、interrupt)():中断线程的sleep休眠状态,通过报异常机制。(但捕捉之后不会影响下面程序继续执行)
5、stop():强行把线程终止,直接把线程干死,已过时,不建议使用,容易丢失数据。(不合理终止线程)
可以采用在run()函数中加判断等于true就运行,在main方法中写入等于false的语句来终止线程。(合理终止线程)
6、setPriority(int):设置线程的优先级,优先级最高10,最低1,默认优先级是5,优先级的高低跟执行顺序没关系,跟抢夺时间片的多少有关系,一般来说,优先级高的线程先执行完。
7、getPriority():获取线程的优先级。
8、yield():让位方法。将线程的时间片让位给其他线程,但是这个操作需要时间,业务逻辑需求的代码谨慎使用。
9、join():插队方法,当前x线程进入阻塞block状态,让线程对象y合并进入该x线程和优先运行,直到y线程对象结束,当前x线程才可以继续执行。
getId() 方法的作用非常简单,就是取得正在运行线程的唯一标识。(相当于是当前线程的编号标识符)
这里是引用实例start():和run(): 启动分支线程和压栈
实例isAlive():判断线程是否处于活动状态 (线程调用start后,即处于活动状态)
静态Thread.sleep():*让位线程方法,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。放弃抢占CPU时间片,让给其他线程使用。
注意sleep方法是静态方法,是休眠和阻塞“调用位置”所处的线程。(该方法要处理异常), 即特指当前线程所处位置的线程后面代码进入休眠状态,即暂停执行。(直到睡眠时间结束或者interrupt中断)
实例interrupt():手动中断线程线程的sleep睡眠(这种中断方式依靠的是,异常处理机制),(用于休眠时间太久,要自己手动中断休息了)
静态Thread.yield(不是100%成功):主动让位线程方法。调用yield就会主动礼让,从运行回到就绪状态,但是回去就绪状态也有可能(小概率)又抢到再次时间片又去抢先运行
实例join():调用join方法的线程强制执行,其他线程处于阻塞状态,等该线程执行完后,其他线程再执行。有可能被外界中断产生InterruptedException 中断异常。
静动态Thread.currentThread().getPriority():/t1.getPriority(); 获取当前或指定线程的优先级。(默认为5,最小1,最大10)
静动态Thread.currentThread().setPriority():/t1.setPriority(): 设置当前或指定线程的优先级 (前一个main,后一个t1)
注意:线程优先级高,被CPU调度的概率大,但不代表一定会运行,还有小概率运行优先级低的线程。
静态wait():导致线程等待,让其进入堵塞状态。该方法要在同步方法或者同步代码块中才使用的(生产者者和消费者模式)
静态notify():通知唤醒当前线程,进入运行状态。该方法要在同步方法或者同步代码块中才使用的(生产者者和消费者模式)
notifyAll():通知唤醒所有等待的线程。该方法要在同步方法或者同步代码块中才使用的
package com.bjpowernode.javaSE.多线程机制.Thread多线程方法;
public class aThread线程的常用方法汇总 {
public static void main(String[] args) {
Test test = new Test();
test.doDome();//main
MyThread t1 = new MyThread(); //继承Thread类来启动线程???????、、、、
t1.setName("t1"); //修改和获取线程名字
System.out.println(t1.getName()); //第一个分支线程的默认名字为:Thread-0 ,修改后为t1
MyThread t2 = new MyThread();
t2.setName("t2");
System.out.println(t2.getName()); //第二个分支线程的默认名字为:Thread-1 ,修改后为t2
Thread currentThread = Thread.currentThread();
// System.out.println(currentThread);//Thread[main,5,main]
System.out.println(currentThread.getName());//如果当前线程对象类出现在主线程的main方法中,那么当前线程对象为main主线程。
//如果当前线程对象类出现在分支线程的run方法中,那么当前线程对象为t1/t2分支线程。(看谁先抢占到时间片就先执行)
t1.start();//启动线程,开辟空间,线程即进入就绪状态,等待CPU调度,而线程准备抢占时间去运行
t2.start();
for(int i=0;i<100;i++){
System.out.println(currentThread.getName()+"主线程main----->"+ i );//运行结果每次都不同,因为每次线程抢夺的时间片可能不同
}
}
}
class MyThread extends Thread{ //第一种方法,直接继承 Thread线程类,MyThread直接充当线程直接启动即可
@Override
public void run() {
for(int i=0;i<100;i++){
Thread currentThread = Thread.currentThread(); //
System.out.println(currentThread.getName()+"分支线程---->" + i); //等于 System.out.println(this.getName()+"分支线程---->" + i);
}
}
}
class Test{
public void doDome(){
// this.getName(); //this指向当前对象的getName()方法,可以该类没有getname方法,因此无法写,但是可以写Thread.currentThread().getName()方法
System.out.println( Thread.currentThread().getName());
}
//除非自己写一个getName方法就能用
/* public static void getName(){
System.out.println(123);
}*/
}
2、反射机制
一、什么是反射:
【1】物理上的反射: 一种光学现象。指光在传播到不同物质时,在分界面上改变传播方向又返回原来物质中的现象。光遇到水面、玻璃以及其他许多物体的表面都会发生反射。当光在两种物质分界面上改变传播方向又返回原来物质中的现象,叫做光的反射
【2】Java的反射: java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。
本质是JVM得到编译器静态编译过来的class对象之后,再通过class对象进行动态的反编译,从而获取对象的各种信息,即可以通过反射机制可以去操作class字节码文件。
【3】因此总结:
①反射就是静态去绑定class对象,实际new实例化时会动态反射回来具体的类对象。
②反编译就是把*.class 文件逆向生成*.java文件。(把.class在控制台逆向输出为java文件形式)*
二、反射原理:
①JAVA中的反射是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变。通俗的讲就是反射可以在运行时根据指定的类名获得类的信息。
②Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
③上面听不懂没关系:上面通俗讲就是之前我们多态讲的javac的静态编译和jvm运行器动态编译。
静态编译:编译时确定类型,绑定等号左边的对象类型,语法没问题即可以通过。
动态编译:运行时确定类型,绑定具体new出来的对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
④因此我们可以明确的看出动态编译的好处,而反射就是正好运用了动态编译创建类对象。 (获取类对象返回obj实际可以动态看具体反射回什么对象)
Class cls= Class.forName(“Customer”); 静态返回Class类对象
Object object =cls.newInstance(); 动态却是new的Customer类对象,这里的Object具体指的是Customer对象
三、反射的工作步骤机制:
程序运行时,java 系统会一直对所有对象进行所谓的运行时类型识别,这项信息记录了每个对象所属的类。通过专门的类可以访问这些信息。用来保存这些信息的类是 class 类,class类为编写可动态操纵的 java 代码程序提供了强大功能
四、反射机制下的常用的类:
(可以获取一个类的类名、修饰符,属性,构造器,具体方法)
①Java.lang.Class: 代表整个类字节码,代表一个类,一个类型 (通过Class可以反编译获得对象类 和 实例化类对象)
②Java.lang.reflect.Constructor: 代表字节码中的构造器字节码 (通过Constructor可以反编译获得类的构造方法)
③Java.lang.reflect.Field: 代表字节码类中的属性字节码(特指类的成员变量) (通过Field可以反编译获得类的成员属性)
④Java.lang.reflect.Method: 代表字节码类中的方法字节码 (通过Method可以反编译获得类的成员方法)
⑤Java.lang.reflect.Modifier:, 代表字节码类中的权限修饰符节码
五、反射的优缺点:
1、优点:(1)在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,
(2)这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
六、反射机制具体开发用途:
1、反编译:xxx.class–>xxx.java文件 (正常编译是java—>class)
【具体是获取一个java类文件的类名、修饰符,属性,构造器,具体方法】
2、通过反射机制先静态可以获得class类属性,再通过class字节码属性动态去实例化类具体某个类对象
(先获得class,然后使用class的newInstance方法,此方法会调用java类对象无参构造方法,从而访问java对象)。
3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean)
为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
【1】例如,在使用Strut2框架的开发过程中,我们一般会在struts.xml里去配置Action,比如
<action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute">
<result>/shop/shop-index.jsp</result>
<result name="error">login.jsp</result>
</action>
比如我们请求login.action时,那么StrutsPrepareAndExecuteFilter就会去解析
struts.xml文件,从action中查找出name为login的Action,
并根据class属性创建SimpleLoginAction实例,并用Invoke方法来
调用execute方法,这个过程离不开反射。配置文件与Action建立了一种映射关系,
当View层发出请求时,请求会被StrutsPrepareAndExecuteFilter拦截,
然后StrutsPrepareAndExecuteFilter会去动态地创建Action实例。
【2】比如,加载数据库驱动的,用到的也是反射。
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
六、深入了解如何获取类对象和实例化对象:
1、先了解如何获取类对象:
package 反射机制简介;
import java.lang.reflect.Modifier;
public class 反编译Class对象类 {
public static void main(String[] args) throws ClassNotFoundException {
//第一种方式获取Class对象,前提new好对象
//(1)引用对象obj.getClass();
Student1 stu1 = new Student1();// new一个Student对象类型,也是一个Class类对象。
Class stuClass = stu1.getClass();//用object.getClass()获取Class类Student对象类型,这里指Student.class
System.out.println(stuClass.getName()); //反射反编译机制.Student
//第二种方式获取Class对象, Student/int/Integer.class任何类型都有class属性。
//(2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
// Class c = 任何类型.class
Class stuClass2 = Student1.class;
System.out.println(stuClass.equals(stuClass2));// true 判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象,用Class类的forName直接获取找到类对象
//(3)通过Class类的静态方法:forName(String className类名)(最常用)
// Class c= Class.forName("包名.类名,");
try {
Class stuClass3 = Class.forName("反射机制简介.Student1");//注意此字符串带包名的类路径,包名.类名
System.out.println(stuClass3.equals(stuClass2));// true 判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
class Student1{
}
反射机制简介.Student1
true
true
2、下面深入了解如何升级获取对象类:(通过配置文件)
前提1:不管使用什么方式读取配置文件,都必须 :前提保证这个配置文件必须得放在src 类目录下
前提2:有五种,前三种拿相对路径读取,后两种拿电脑绝对路径获取,各有一种推荐
前提3:不管什么路径最好不要出现中文:包括:文件路径+包名+类名都是路径的一部分,否则很容易出现乱码和报错
前提4:自定义的配置文件必须以properties结尾
第一种方式: 常规固定写死String路径形式的类名(不灵活,但代码简洁,包名可以有中文)
//第一种常规方式:
//固定写死String路径形式的类名(不灵活,但代码简洁)
Class c1 = Class.forName("IOpropertiesTOgetClassObject.User"); //类所在包名 + 类名
Object obj1 =c1.newInstance();
System.out.println(obj1);//不重写toString:反射反编译机制.IO和Properties和Class类联合.User@677327b6
System.out.println(obj1.toString());//重写后:User{name='涂岳新', age=21}
第二种方式:
IO字节/字符流 + Properties();(src下包名不能有中文)
①读取properties配置文件中所涉及到的classname=Student 类名【包+类】不要出现中文,因为读取出来会乱码,
②最好把“自定义类”直接创建在src下,不建包,这也配置文件中就能直接写类名不用加包名了。
③IO+Properties() 一般用于读取idea内置配置文件,读取key=value形式的内容
package IOpropertiesTOgetClassObject;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class 读取配置文件方式1IO和properties联合 {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, IOException {
//第二种方式:IO+Properties()
//下面两个例子联合IO+Properties() 读取类名路径(符合OPC,去配置文件写类名路径即可)
//字节流先读取字符内容
FileInputStream r=new FileInputStream("reflect反射机制\\src\\IOpropertiesTOgetClassObject\\获取类对象.properties");
Properties p=new Properties(); //只能是以key-value键值对儿存储“String字符串”
p.load(r); //通过 Properties的load方法来加载来读取
String classname= p.getProperty("classname"); //再使用getProperty()方法把自定义文件中key对应value读取出来,读取出来的就是一个"类名路径"
//重点!!!!!!!!!!!!(读取配置属性文件的类名,来实例化对象)
Class c=Class.forName(classname);// 通过“类名路径”来获取类,把固定的String类名路径变为灵活的变量classname
Object obj=c.newInstance(); //再通过类来获取实例化类对象
System.out.println(obj.toString());//Person{name='王啪啪', age=22}
String classname1= p.getProperty("classname1");
Class c2=Class.forName(classname1);// 通过“类名路径”来获取类,把固定的String类名路径变为灵活的变量classname1
Object obj2=c2.newInstance();
System.out.println(obj2);//Wed Nov 11 20:31:23 CST 2020
}
}
class Person{
String name;
int age;
public Person() {
name = "王啪啪";
age = 22;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class User{
String name;
int age;
public User() {
name = "涂岳新";
age = 21;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
第三种方式(推荐简洁方便): 使用sun公司写好的资源绑定器Bundle,
①可以直接获得配置文件的内容:
ResourceBundle resourceBundle = ResourceBundle.getBundle(" ")????
②前提这个配置文件必须得放在src 类目录下,并且以properties结尾。(自定义的配置文件必须以properties结尾。)
③但最终在资源绑定器写这个类路径名的时候不能写扩展名properties!(死记)
public class 读取配置文件方法2资源绑定器ResourceBundle {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, IOException {
//第三种方式:resourceBundle.getString("classname2");直接读取配置文件属性
ResourceBundle resourceBundle = ResourceBundle.getBundle("IOpropertiesTOgetClassObject\\IdeaDocumentAbsolutionPath");
String classname = resourceBundle.getString("classname2");
//实例化类对象
Class c=Class.forName(classname);// 通过“类名路径”来获取类,把固定的String类名路径变为灵活的变量classname
Object obj=c.newInstance(); //再通过类来获取实例化类对象
System.out.println(obj);//VIP@677327b6
}
}
VIP@677327b6
VIP@7f31245a
第四种方法:
(不推荐,因为如果文件路径有中文不是乱码而是直接报错)
1、番外:先了解如何获取一个当前Idea文档文件在他人电脑中的绝对路径。(我们只知道相对路径)
①绝对路径即你不知道放具体哪个磁盘下文件夹下,你想通过运行代码知道)那么就使用:
String path = Thread.currentThread().getContextClassLoader().getResource(" xxx").getPath();
②现在idea直接右键点击Copy path也可以直接找到。如下直接复制即可:哈哈哈
D:\java idea\idea代码文件\reflect反射机制\src\ IOpropertiesTOgetClassObject \IdeaDocumentAbsolutionPath.properties
③如果电脑绝对路径像上面一样有中文的话,那么运行代码会出现乱码。(以下就出现了)
String path = Thread.currentThread().getContextClassLoader().getResource("IOpropertiesTOgetClassObject/IdeaDocumentAbsolutionPath.properties").getPath();
System.out.println(path);
//必须只能是自定义配置文件,不能是java类文件
//D:/java%20idea/idea%e4%bb%a3%e7%a0%81%e6%96%87%e4%bb%b6/out/production
// /%e5%8f%8d%e5%b0%84%e5%92%8c%e6%b3%a8%e8%a7%a3%e6%9c%ba%e5%88%b6/IdeaDocumentAbsolutionPath.properties
第五种方法(推荐):
①Thread绝对路径直接返回流 ,(不用+IO流了)+ properties 读取idea内置配置文件
② InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("");
③优点:可以不用IO了,不用IO读取到中文路径乱码就不会报错到。
下面展示当我们直到绝对路径之后如何,读取配置文件了:
public class 读取配置文件方法2资源绑定器ResourceBundle {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, IOException {
//第四种方法:用返回电脑文件绝对路径Thread方法,直接返回路径变量读取配置文件 ,
// Thread方法+(+IO流FileReader) + properties 读取idea内置配置文件
//以下代码是报错的:Exception in thread "main" java.io.FileNotFoundException:
String path = Thread.currentThread().getContextClassLoader().getResource("IOpropertiesTOgetClassObject\\IdeaDocumentAbsolutionPath.properties").getPath();
System.out.println(path);
FileReader reader = new FileReader(path); //写IO流的“idea内置路径”那里不用直接写idea的内置路径,直接写path变量了(灵活不写死OPC)
Properties p = new Properties(); //只能是以key-value键值对儿存储“String字符串”
p.load(reader); //通过 Properties的load方法来加载来读取
String classname2= p.getProperty("classname"); //再使用getProperty()方法把自定义文件中key对应value读取出来,读取出来的就是一个"类名路径"
//Class stuClass3 = Class.forName("反射机制简介.Student");//原来之前是写死的固定类名【包+类】
Class c2 = Class.forName(classname2);//现在把固定的类名,变为灵活的变量classname.(随配置文件的更改而更改,不用在代码里面改,OPC)
Object obj2 = c2.newInstance(); //再通过类来获取实例化类对象
System.out.println(obj2);//
//上面这种方式存在缺点就是idea一旦在电脑绝对路径出现了中文路径,Thread那么读取出来就会出现乱码,因此乱码path放入IO流时就会报错
//建议使用下面方法Thread绝对路径直接返回流,不用创建IO放乱码路径。
//第五种方法:Thread绝对路径直接返回流 ,(不用+IO流了)+ properties 读取idea内置配置文件
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("IOpropertiesTOgetClassObject/IdeaDocumentAbsolutionPath.properties");
Properties p1 = new Properties();
p1.load(inputStream); //通过 Properties的load方法来加载来读取,字符串形式的配置文件
String classname1= p1.getProperty("classname1"); //再使用getProperty()方法把自定义文件中key对应value读取出来,读取出来的就是一个"类名路径"
//实例化类对象
Class c1 = Class.forName(classname1);// 通过“类名路径”来获取类,把固定的String类名路径变为灵活的变量classname
Object obj1=c1.newInstance(); //再通过类来获取实例化类对象
System.out.println(obj1);//VIP@7f31245a
}
}
八、下面来具体讲述如何通过反射机制反编译出java文件:
【具体是反编译获取一个java类文件的类名、修饰符,属性,构造器,具体方法】
下面开始实操:通过反射机制反编译下面Customer类的所有信息:
大致了解一下实现步骤:
【1】反射机制Class类,作用?(很重要,一环扣一环的)
1、可以获得某个类的class类属性,(类的class属性十分重要!!!,任何反编译前提都要实现这一步)。
1、class类属性去实例化出某类object对象。forName+newInstance()。(自动调类的无参构造,需要重写toString)
2、class类属性还能把class当中的属性类、方法类、构造器类调取出来用于反编译。(class.getDeclaredFields/Methods/constructor())
3、object相当于是一个类引用obj,用于下面四个类中的具体成员属性和普通方法的访问调用。(下面要用到有)
/
【2】反射机制Method类,可以调用某类的方法。 method.invoke(object) method.setAccessible(true);(访问私有方法需要先打破封装)
【3】反射机制Field类,可以获取和修改某类成员变量。 field.get/set(object) field.setAccessible(true);(访问私有属性需要先打破封装)
【4】反射机制Constructor类,可以获得某类的构造方法。constructor1.newInstance();/ constructor2.newInstance(123,123,true,“tuyueixn”);
通过无参/有参构造器可以实例化无参/有参对象。 (ps,class.newInstance();只能实例化无参对象)
【5】反射机制Modifier类(很少用死记即可),可以获得某类的某个部分的权限修饰符。Modifiers.toString(class/field/constructor/method.getModifiers),
返回String类型,可以获取Class类、Field属性,Method方法,Constructor构造方法的权限修饰符。
public class Customer extends Object implements Person {
//四个Field属性,分别采用四个不同的访问权限修饰符。
public int no;
protected int age;
boolean sex;
private String name;
public Customer() {
}
public Customer(int no, int age, boolean sex, String name) {
this.no = no;
this.age = age;
this.sex = sex;
this.name = name;
}
public void eat(int arg , String args){ //吃方法
System.out.println("eat...公开方法调用了");
}
private void play(){ //玩方法
System.out.println("play...私有方法调用了");
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer{" +
"no=" + no +
", age=" + age +
", sex=" + sex +
", name='" + name + '\'' +
'}';
}
}
interface Person{
}
1、反编译获取Class类名:
一、Class类的方法:
(整个类结构:类的权限修饰符 + class + 类名 +extends + 父类名 + implements + 实现的接口名) {}
1、Class.forName(类路径;)静态方法,获得对应类对象的Class属性
2、class.newInstance(); 通过已获取的Class类可以获取(实例化)类对象。,返回一个Object
3、“完整类名,带包名”=class.getName();
“简名,不带包名”= class.getSimpleName();
4、class.getFields():获取类中仅仅是类的公开属性,返回一个Field[] (通过类获得Field)
5、class.getMethods():获取类中仅仅是类的public公开方法,返回一个Method[]
6、class.getDeclaredFields(无参/有参):获取类中所有/特指属性,返回一个Field[]/Field
7、class.getDeclaredMethods(无参/有参):获取某类中所有/特指方法,返回一个Method[]/Method
8、class.getDeclaredConstructors(); 获取某类中所有/特指构造器,返回一个Constructor[]/Constructor
9、Modifier.toString(cls.getModifiers()); 获取类权限修饰符
//进入正题反编译Class
System.out.println("=========反编译获取对象类==========");
//整个类结构:(权限修饰符 + class + 类名 +extends + 父类名 + implements + 实现的接口名) {}
//1、反射机制,获取Customer对象的class属性
Class cls= Class.forName("Customer"); //更改类名随意访问任何类。[包+类] java.lang.String
//2、获取类的权限修饰符
String modifier= Modifier.toString(cls.getModifiers());
System.out.print(modifier); // public 只是这里的Worker类省略了public,已经存在public了
//不换行一直轮流输出
//3、获取简单类名,cls.getName();是全名(包+类)
String className=cls.getSimpleName();
System.out.print(" class "+className); //Worker
//不换行一直轮流输出
//4、获取继承的父类(java只能继承一个父类)
String superClassName=cls.getSuperclass().getSimpleName();
System.out.print(" extends "+ superClassName);
//不换行一直轮流输出
//5、获取实现的接口(java可以实现多个接口)
System.out.print(" implements ");
Class[] interfaces = cls.getInterfaces(); //为什么返回一个接口数组,因为java允许实现多个接口,继承只允许继承一个父类。
for (int i = 0; i < interfaces.length; i++) { //遍历接口数组
System.out.print(interfaces[i].getSimpleName()); //输出第一个接口名字
if(i<interfaces.length-1){ // 当前接口数<整个接口数组长度 说明还有接口要实现
System.out.print(" , "); //输出,号
}else{
System.out.println(" { ");//直到else这里,说明没接口要实现了,输出{ 号
}
}
//不换行一直轮流输出
System.out.println("}");
}
}
=========反编译获取对象类==========
public class Customer extends Object implements Person {
}
3、反编译获取类成员属性:
二、Field成员属性方法:(属性构成:权限修饰符 + 属性类型名 +声明变量名 ) (只能获得成员属性,无法获得局部变量)
1、Modifier.toString(field[i].getModifiers()); (获取属性权限修饰符)
2、field.getType().getName(); 获取类属性的类型的名字
3、filed.getName(); 获取类属性声明变量的具体名字
4、field.get/set(object); 访问和修改属性。 (field.setAccessible(true);(访问私有属性需要先打破封装))
package 反射机制简介;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ResourceBundle;
public class 反编译Field属性类 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//反射机制,获取Customer对象的class属性
//反射Customer类中的所有Field属性,Customer类在src目录下
//以下是一个框架,复习一下专门通过ResourceBundle来读取配置文件,再去获得类来实例化对象。
ResourceBundle resourceBundle = ResourceBundle.getBundle("反射机制简介\\Field"); //用ResourceBundle读取配置文件不能带properties后缀,从src下面的包开始写路径
String classname= resourceBundle.getString("classname");
Class customerClass = Class.forName(classname); //获得Customer类
Object object = customerClass.newInstance(); //实例化类对象,等于new Customer调取其无参构造方法。
System.out.println(object.toString());//Customer@677327b6,因为没重写toString方法
System.out.println("完整类名,带包名"+customerClass.getName());
System.out.println("简名,不带包名"+ customerClass.getSimpleName());
//进入正题,获取类中所有的Field"公开"的属性。(!!!!!!!!这方法没什么用。被权限卡死了)
Field[] field1 = customerClass.getFields();
System.out.println(field1.length);//四个属性只有1个能被访问到
System.out.println(field1[0].getName());//看看这唯一一个属性是谁,getName拿到属性的名字? no属性,是public权限修饰符修饰的
//以下才是正题
//1、 通过class属性,获取所有的Customer类的Field属性。
Field[] field2=customerClass.getDeclaredFields();
System.out.println(field2.length);//4个,全部能访问到,不管任何权限
//2、通过class属性,获取Customer类的某个特定的属性值 customerClass.getDeclaredField("name");
//3、get(object)获取,获取某个field属性赋值
Field namefield =customerClass.getDeclaredField("name");
namefield.setAccessible(true);//因为name是私有的,因此要使用setAccessible方法打破封装,这也是反射机制的缺点。给不法分子读取封装代码
System.out.println(namefield.get(object));//null , 这里object充当对象引用自动指向当前name属性
//3、set(object)修改,即给某个field属性赋值
Field nofield = customerClass.getDeclaredField("no"); //name/age/no都行
nofield.set(object,123);//给第38行的object对象,这里既是顾客对象的no赋值123
System.out.println(nofield.get(object));// 123, 这里object充当对象引用自动指向当前name属性
//遍历所有属性,即遍历数组
Field[] field3=customerClass.getDeclaredFields();
for (Field f : field3){
System.out.print(f); //获取引用属性全部东西,一般不要这么用。
System.out.print(" ");
System.out.print(f.getModifiers()); //获取属性的修饰符列表个数,返回值是int类型
System.out.print(" ");
System.out.print(Modifier.toString(f.getModifiers()));// 可以再次通过Modifiers.toString(field.getModifiers)转换为具体的修饰符
System.out.print(" ");
System.out.print(f.getType().getName()); //获取属性的类型全名
System.out.print(" ");
System.out.print(f.getType().getSimpleName());//获取属性的类型简名
System.out.print(" ");
System.out.print(f.getName()); // 获取属性声明名字,no age sex name
System.out.print(" ");
System.out.println();
/*
public int Customer.no 1 public int int no
protected int Customer.age 4 protected int int age
boolean Customer.sex 0 boolean boolean sex
private java.lang.String Customer.name 2 private java.lang.String String name
*/
}
//进入终极正题
//反编译Field,获取所有的成员变量
System.out.println("=========反编译获取成员变量==========");
Field[] fields = customerClass.getDeclaredFields();
for (int i = 0; i <fields.length ; i++) {
//1、获取成员变量的权限修饰符名称
String fieldModifier =Modifier.toString(fields[i].getModifiers());
System.out.print("\t" + fieldModifier + " "); //制表符(自动保持缩进),+ 权限修饰符 + 空格
//2、获取成员变量的属性类型的简单名称
String fieldName=fields[i].getType().getSimpleName(); // + 属性类型
System.out.print(fieldName+" ");
//3、获取成员变量的属性名
String name=fields[i].getName(); //属性声明名称
System.out.println(name + ";"); //最后+ ;号 再空行
}
}
}
4、反编译获取类构造器:
Constructor构造器方法:(构造方法构成:权限修饰符 + 构造名+ 无参/有参形式列表[属性类型名 +声明变量名])
1、Modifier.toString(constructors[i].getModifiers()); (获取构造方法权限修饰符)
2、class.getSimpleName(); (获取构造名:Constructor类方法没有直接获取简单构造名,会带包名。因此可以用简单类名代替构造名)
3、Class[] pramTypes=constructors[i].getParameterTypes();(获取构造器的参数类型)
3、pramTypes[j].getSimpleName();(获取形参属性类型名:再获取构造方法中的形式参数的属性类型名)
4、fields[j].getName(); (获取形参声明变量名:造方法的参数名是自定义的局部变量本来无法获取的,但是一定是和成员变量属性名一样的。因此可以使用field的getName方法)
5、Constructor constructor1=cls.getDeclaredConstructor(有参/无参); -----》 constructor1.newInstance();/ constructor2.newInstance(123,123,true,“tuyueixn”);
通过无参/有参构造器可以实例化无参/有参对象。 (ps,class.newInstance();只能实例化无参对象)
public class 反编译constructor构造类 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//反射机制,获取Customer对象的class属性,永远第一步
Class cls= Class.forName("Customer"); //更改类名随意访问任何类。[包+类] java.lang.String
String className=cls.getSimpleName();
//通过class属性获取Field属性类
Field[] fields = cls.getDeclaredFields();
System.out.println(fields[0]); //public int Customer.no 第一个属性全称
System.out.println(fields[0].getName()); //no
Constructor constructor1=cls.getDeclaredConstructor(); //获取某个构造方法,这里是无参会给予默认值
Object object1 = constructor1.newInstance();
System.out.println(object1); //需要重写好toString Customer{no=0, age=0, sex=false, name='null'}
Constructor constructor2=cls.getDeclaredConstructor(int.class,int.class,boolean.class,String.class); //有参
Object object2 = constructor2.newInstance(123,123,true,"tuyueixn");
System.out.println(object2); //Customer{no=123, age=123, sex=true, name='tuyueixn'}
System.out.println("=========下面开始反编译获取构造器==========");
// Constructor构造器方法:(构造方法构成:权限修饰符 + 构造名+ 无参/有参形式列表[属性类型名 +声明变量名])
//反编译Constructor,获取全部(无参+有参)构造器
Constructor[] constructors=cls.getDeclaredConstructors();//先通过class获取构造器
//进入for循环出所有构造器:分别获取权限修饰符 + 构造名+ 无参/有参形式列表[属性类型名 +声明变量名])
for (int i = 0; i < constructors.length; i++) {
//1、获取无参和有参构造器的权限修饰符
String constructorModifier = Modifier.toString(constructors[i].getModifiers());
System.out.print("\t" + constructorModifier+" ");
//2、获取无参和有参构造器名,
// 在Constructor中无法直接获取构造器的简单属性名,但是构造器的属性名和类名是一样的,所以可以直接使用类名即可
// System.out.print(constructors[i].getName()+"(");//反射机制简介.Worker(),会带上包名,没有getSimpleName();因此只能使用类名
System.out.print(className + "(");
//3、获取构造器的形式参数列表,如果是无参构造则跳过第二个循环到最下面的右括号
Class[] pramTypes=constructors[i].getParameterTypes(); //这里的构造器属性参数prams[j]才是和field[i]属性一样
for (int j = 0; j < pramTypes.length ; j++) {
String pramTypesName= pramTypes[j].getSimpleName(); //构造器属性类型的名字
System.out.print(pramTypesName + " " + fields[j].getName());//构造器属性类型名 + 属性声明名
//构造方法的参数名,一定是和成员变量属性名一样的。因此可以使用field的getName方法
if(j < pramTypes.length-1){ //如果还有参数类型则加,号
System.out.print(" , ");
}
}
//无参构造的话直接跳过形式参数列表的for循环,无参的直接就右括号即可结束。
System.out.print(")");//这里的括号要放这里是因为,这里有一个无参构造很特殊,不用进入第二个for循环直接跳到这里的
System.out.println("{}");
}
}
}
5、反编译获取类方法:
四、Method方法:(方法构造:权限修饰符 + 返回值类型 + 方法名 + 无参/有参形式参数[属性类型名 +声明变量名])
1、Modifier.toString(methods[i].getModifiers()); (获取权限修饰符)
2、method.getReturnType().getSimpleName(): (获取返回值类型)
3、methods[i].getName(); (获取方法名)
4、method.getParameterTypes().getSimpleName():(获取方法中的形式参数的属性类型名)
5、方法的参数声明变量名是局部变量自定义的,也不一定和成员属性名一致,因此只能用"args"代替了。
6、method.invoke(实例object):调用方法,根据实例配置的参数去调用方法 ( method.setAccessible(true);(访问私有方法需要先打破封装))
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class 反编译方法Method方法类 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//反射机制,获取Customer对象的class属性
Class cls= Class.forName("Customer"); //更改类名随意访问任何类。[包+类] java.lang.String
Object object =cls.newInstance(); //实例化类对象,等于new Customer调取其无参构造方法。
System.out.println(object.toString());//Customer@677327b6,因为没重写toString方法
//1、先通过class属性,获得某类的所有方法,返回一个方法数组Method[]。
Method[] methods1 = cls.getDeclaredMethods();
//2、先通过class属性,获得某类的特指的方法,返回一个方法类型Method
Method playMethod = cls.getDeclaredMethod("play");//获得Customer类的公开play无参方法
Method eatMethod = cls.getDeclaredMethod("eat",int.class,String.class); //获得Customer类的私有eat有参方法。
//3、再通过反射机制Method类,再去调用某类的方法. 特殊这里需要使用invoke方法
playMethod.setAccessible(true);//打破封装,才能访问私有方法
playMethod.invoke(object); //调用无参私有方法,需要先打破封装。object特指实例化出来的Customer类的xxx方法
//play...私有方法调用了
eatMethod.invoke(object,123,"涂岳新"); //有参公开方法,直接调用即可 object特指实例化出来的Customer类的xxx方法
//eat...公开方法调用了
System.out.println("=========获取成员方法==========");
//框架反编译Method ,先获取所有成员方法,返回一个Method[]数组
Method[] methods = cls.getDeclaredMethods();
//遍历所有方法出来
for (int i = 0; i < methods.length ; i++) {
//获取方法的权限修饰符 (修饰符)
String methodModifier = Modifier.toString(methods[i].getModifiers());
System.out.print("\t" + methodModifier+" ");
//获取方法的返回值的属性名 (返回值)
String methodSimpleName=methods[i].getReturnType().getSimpleName();
System.out.print(methodSimpleName+" ");
//获取自定义的的方法名,(方法名)
String methodName=methods[i].getName();
System.out.print(methodName);
System.out.print("(");
//获取所有形式参数列表 (所有形参属性名,一个方法形参属性可能有多个,因此也是一个数组)
Class[] pramTypes=methods[i].getParameterTypes(); //第i个方法的所有参数,返回一个Class数组。
for (int j = 0; j <pramTypes.length ; j++) { //遍历形式参数数组,输出一个方法所有形式参数
String pramName=pramTypes[j].getSimpleName();
System.out.print(pramName + " "+ "args"); //(参数类型名 + 参数变量名 )
//因为普通方法的参数名,不一定是和属性名一样的。因此不能使用field的getNam,只能"加个字符串args"。
if(j <pramTypes.length-1){
System.out.print(" , ");
}
}
System.out.print(")");
System.out.println("{}");
}
}
}
注解机制
一、Annotation注解简介:
【1】概念:是一种引用数据类型,编译生成之后也是class文件(类似一个自定义引用类,是给编译器看的一种注释)
【2】注解类的语法定义:[修饰符列表] @interface 注解类型名{} (专门放程序解释的一个容器)
【3】注解的使用格式:
① @注解类型名 (左键点进去注解类容器可以让编译器看到对这段代码的解释)
② 注解是以”@注释名”在代码中存在,还可以添加一些参数
值,例如@SuppressWarnings(value=”unchecked”)。
二、注解可以出现并用在什么地方?
class类上、
field属性上(variable任何变量)
method方法上、
interface接口上、
Enum枚举上、
Annotation注解类型上(套娃注解)
三、注解分类:
①注解分为:(JDK内置注解,JDK内置元注解,自定义注解,自定义元注解)
②元注解:如果一个注解用来修饰套娃注解,那么这个注解被称为“元注解”。
四、JDK中有哪些内置的注解:
【1】@Override:@Override注解只能用于标注方法,标识@Override注解的方法必须是父类重写的方法,专门是给编译器参考的,如果不是父类重写的方法,编译器会报错:
(只在编译阶段起作用,和运行期无关)
【2】@Deprecated :标识的方法不建议使用,Deprecated这个注解标注的是已过时
【3】@SuppressWarnings:用来抑制编译时的警告信息@SuppressWarinings ,需要提供参数才能正常使用,这些参数都是已经定义好的,我们只需要选择就可以了。
五、JDK内置元注解: 是标注注解的注解(套娃注解)
JDK常见的元注解:target和Retention (还有@Documented 和 @Inherited)
【1】target,是标注“注解类型”的注解,
作用:是决定标注的注解能出现在什么地方。(即使用范围)
举例:@Target(value=ElementType.TYPE)
(这里ElementType是一个枚举,里面有Type类、Method方法、Field属性等旗子)
【2】Retention,也是标注“注解类型”的注解,
作用:是决定标注的注解最终只能保存到哪里。(即最终保存地点)
举例:@Retention(RetentionPolicy.SOURCE) 表示该注解最终保存的java、源文件当中 ( RetentionPolicy是一个枚举,里面有SOURCE CLASS RUNTIME三个旗子)
@Retention(RetentionPolicy.CLASS) 表示该注解最终保存的class字节码文件当中
@Retention(RetentionPolicy.RUNTIME) 表示该注解最终保存的class字节码文件当中,并且可以被反射机制去反编译读取出来
【3】总结:JDK内置的:@override/@Deprecated/@SuppressWarnings等注解,
能出现在哪里由元注解target决定和最终保存点由元注解Retention决定。
六、自定义注解:
自定义注解的语法:使用 @interface 定义 自定义注解时,自动继承了java.lang.annotation.Annotation 接口
1)@interface 用来声明一个注解
2) 其中的每一个方法实际上是声明了一个配置参数 int age();
a) 方法的名称就是参数的名称 int age();
b) 返回值类型就是参数类型(返回值类型只能是基本类型、Class、String、enum)
c) 可以通过 default 来声明参数的默认值 int age() default 25
d) 如果只有一个成员,一般参数名为 value
注意事项:注解元素必须要有值。我们定义注解元素时,经
常使用空字符串,0 作为默认值。也经常使用负数(比如-1)表示不存在的含义
七、注释VS注解:
总结:
【1】注释是供人看的,注解是供计算机程序调用的。
【2】 一种是程序员写给另一个程序员的,一种是程序员写给计算机解析的。
一、注释:(1、// 2、/* * / 3、/** */)
【1】前两种编译器直接跳过从来不阅读,是给自己和别人方便阅读的。
【2】第三种编译器是可以看懂的,当你使用javadoc这样的命令时会用到,用来生成API时用的。
二、注解:
【1】注解完全就是给编译器看的。 比如@Ovrride表示这个方法是重写了父类中的方法,而不是自定义的,所以这个时候编译器会去检查你的方法名是否和父类一样,是否写错了。
【2】起初,注解是比较简单的,后来注解里面可以加入变量和参数,以节省代码(这些代码都是大家共同认可的,用一个公式给代替了)
【3】注解是给计算机继承的解释说明,也可以不用注解,自己写代码告诉计算机编译器。 注解其实就是代码,只是看起来和我们自己写的有点不一样而已。 也是代码的一部分,学习Hibernate和Spring等的时候会大量用到注解,用来节省大量代码。
【4】 注解具体在JAVA编程的作用?
1)注解可以给一个类名的类名/属性/方法等 去配置一个相信信息,并且反编译可以读取出来。(具体看下面反编译注解)
2)注解还可以规范代码特定格式和特定需求,是给编译器看的一种注释,如果没有达到被注解标记的东西没有达到注解的需要就会报错。
注解基本概念:
package 注解机制.注解机制基础;
public class 注解机制简介 {
public static void main(String[] args) {
}
}
@MyAnnotation //用在类上
@OtherAnnotation
class Annotation{
@MyAnnotation //用在属性上
private int no;
@MyAnnotation//用在任何方法上
public Annotation(int no) {
this.no = no;
}
@MyAnnotation
public static void m1(){
@MyAnnotation //用在局部变量上
int i =1;
System.out.println("m1静态方法执行了");
}
@MyAnnotation
public void m2(){
System.out.println("m2实例方法执行了");
}
}
@interface MyAnnotation{ // 注解的语法定义:[修饰符列表] @interface 注解类型名{ }
}
@MyAnnotation //用在其他注解类型上,套娃注解,对注解的注解。
@interface OtherAnnotation{
}
七、注解类如何定义内部属性和定义规则:
//1、在注解容器中可以定义属性,但如果一个注解里面定义了属性,那么在使用注解的时候一定要给属性赋值,否则编译报错(定义了几个属性,就要给几个属性赋值)
//2、属性值也可以在使用注解省略,即在定义属性的后面 + default + 对应类型默认值。(这样就在赋予了初始值,就可以不用再次赋值了)
//3、如果注解定义的属性只有一个,并且该属性的属性名是 value 的话 ,都符合这两个条件则在使用注解去赋值的时候,value属性名可以不写。
//4、能被定义属性有:byte char short int long boolean String Class 枚举enum类 以及 前面全部的数组形式
package 注解机制.注解机制基础;
public class 注解容器内部定义属性 {
public static void main(String[] args) {
}
// @MyAnnotation1 直接为什么会报错?
//1、因为如果一个注解里面定义了属性,那么在使用注解的时候一定要给属性赋值,否则编译报错(定义了几个属性,就要给几个属性赋值)
//2、属性值也可以在使用注解省略,即在定义属性的后面 + default + 对应类型默认值。(这样就在赋予了初始值,就可以不用再次赋值了)
//3、如果注解定义的属性只有一个,并且该属性的属性名是 value 的话 ,都符合这两个条件则在使用注解去赋值的时候,value属性名可以不写。
//4、能被定义属性有:byte char short int long boolean String Class 枚举enum类 以及 前面全部的数组形式
//5、当注解定义的属性是数组的时候?
@MyAnnotation1(name ="涂岳新",age = 25)
public static void m1(){
}
@MyAnnotation2 //使用default之后,即使不赋值也不会报错了
public static void m2(){
}
@MyAnnotation3("123" ) //如果注解定义的属性只有一个,并且该属性的属性名是 value 的话 ,符合这两个条件则在使用注解去赋值的时候,属性名可以不写。
public static void m3(){
}
@MyAnnotation4(value = "123", age = 25, s =Season.Autumn ) //注解定义的属性有两个,则value属性名不能省略。
public static void m4(){
}
@MyAnnotation5(b=true , str = {"tuyuexin","linruiyi"} , i={1,2,3})
public static void m5(){
}
}
@interface MyAnnotation1{ //在注解容器中可以定义属性;
String name();//语法结构:类型 + 变量名 + ()括号
int age();
}
@interface MyAnnotation2{
String name() default "tuyuexin";
int age() default 25;
}
@interface MyAnnotation3{
String value();
}
@interface MyAnnotation4{
String value();
int age();
Season s();
}
@interface MyAnnotation5{
boolean b();
String[] str();
int[] i();
}
enum Season{
Spring,Summer,Autumn,Winter
}
八、反编译注解开发:
如何反编译获取类名/方法/属性等上面的注解对象。
(以及访问其内部各自的属性)
1、一定确保写了元注解Retention的RUNTIME,xx注解能被反射机制反射,这样才能反编译注解!!!
package 注解机制.reflect注解和访问注解属性;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@MyAnnotation6(value = "类名的注解", name ="涂岳新" )
public class AnnotationTest {
@MyAnnotation6(value ="age属性的注解" , name = "年龄")
int age;
@MyAnnotation6(value = "no属性的注解", name ="学号" )
int no;
String name;
@MyAnnotation6(value = "方法的注解", name ="play" )
public void play(){
}
}
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) //说明 MyAnnotation6注解只能被标注在类和方法和属性上
@Retention(RetentionPolicy.RUNTIME)
//说明MyAnnotation6 最终被保存到class文件中并且可以被反射
// (没这个无法被反射,反编译会报错)
@interface MyAnnotation6 {
String value() ;
String name() ;
}
package 注解机制.reflect注解和访问注解属性;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class 反射反编译注解 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, NoSuchFieldException {
//一、获取类名上的注解对象
//1、先获取使用了该注解的类对象。(因为注解是依托在一个类中的)
Class annotationClass = Class.forName("注解机制.reflect注解和访问注解属性.AnnotationTest");
//2、然后判断AnnotationTest类的某个方法名上是否有MyAnnotation6注解对象
if (annotationClass.isAnnotationPresent(MyAnnotation6.class)){
//3、如果类名上有注解对象的话就通过类名去创建类名上的注解对象。
MyAnnotation6 classAnnotation = (MyAnnotation6) annotationClass.getAnnotation(MyAnnotation6.class);
//4、获取了类名上的注解对象之后就可以 访问整个类名注解对象地址,以及分别访问其内部的属性
System.out.println("MyAnnotation6类名注解对象内存地址为:"+classAnnotation);//MyAnnotation6类名注解对象内存地址为:@注解机制.reflect注解.MyAnnotation6(value=类名的注解, name=涂岳新)
System.out.println( classAnnotation.value()); //类名的注解
System.out.println(classAnnotation.name()); //涂岳新
}
//二、获取方法名上的注解对象
//1、先反射获取使用了该注解的类对象。(因为注解是依托在一个类中的)
Class annotationClass2 = Class.forName("注解机制.reflect注解和访问注解属性.AnnotationTest");
//2、再反射获取类对象中的play方法。
Method playmethod = annotationClass2.getDeclaredMethod("play");
//3、接着判断AnnotationTest类的play方法名上是否有MyAnnotation6注解对象
if (playmethod.isAnnotationPresent(MyAnnotation6.class)){
//4、方法名上有注解对象的话就通过方法名去创建方法名上的注解对象。
MyAnnotation6 methodAnnotation = (MyAnnotation6) playmethod.getAnnotation(MyAnnotation6.class);
//5、获取了方法上的注解对象之后就可以 访问整个方法注解对象地址,以及分别访问其内部的属性
System.out.println("MyAnnotation6类方法上的注解对象内存地址为:"+methodAnnotation);//@注解机制.reflect注解.MyAnnotation6(value=方法的注解, name=play)
System.out.println(methodAnnotation.value()); //方法的注解
System.out.println(methodAnnotation.name()); //play
}
//三、获取属性上的注解对象
//1、先反射获取使用了该注解的类对象。(因为注解是依托在一个类中的)
Class annotationClass3 = Class.forName("注解机制.reflect注解和访问注解属性.AnnotationTest");
//2、再反射获取AnnotationTest类对象中的no属性。
Field noField = annotationClass3.getDeclaredField("no");
//3、接着判断AnnotationTest类的no属性上是否有MyAnnotation6注解对象
if (noField.isAnnotationPresent(MyAnnotation6.class)){
//4、no属性名上有注解对象的话就通过属性名去创建属性名上的注解对象。
MyAnnotation6 fieldAnnotation = (MyAnnotation6) noField.getAnnotation(MyAnnotation6.class);
//5、获取了属性上的注解对象之后就可以
//访问整个属性注解的对象地址,以及分别访问其内部的属性
System.out.println("MyAnnotation6类属性上的注解对象内存地址为:"+fieldAnnotation);
//MyAnnotation6类方法上的注解对象内存地址为:
//@注解机制.reflect注解.MyAnnotation6(value=no属性的注解, name=学号)
System.out.println(fieldAnnotation.value()); //no属性的注解
System.out.println(fieldAnnotation.name()); //学号
}
}
}
本文地址:https://blog.csdn.net/weixin_47592060/article/details/111727057
上一篇: 手机掉了都不要
下一篇: 每日一道力扣题(两数相加)