在 Java 多线程环境中,锁是确保共享资源线程安全的重要手段。

参考文章:

Java锁详解

全网最完整Java学习笔记

基本介绍

什么是锁

在 Java 多线程环境中,锁是确保共享资源线程安全的重要手段。 当线程要操作共享资源时,需先获取对应的锁,以此保证在操作过程中,该资源不会被其他线程访问。待操作结束后,线程释放锁,使其他线程有机会获取并操作该资源。

为什么需要锁

锁可以确保多个线程之间对共享资源的访问是互斥的,也就是同一时刻只有一个线程能够访问被保护的共享资源,从而避免并发访问带来的数据不一致性和竞态条件等问题,是解决线程安全问题常用手段之一。

什么是死锁及如何防止死锁

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。防止死锁有如下方式:

  • 尽量使用 tryLock(long timeout, TimeUnit unit) 的方法 (ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
  • 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
  • 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
  • 尽量减少同步的代码块

Synchronized锁

synchronized 由一对 monitorenter/monitorexit 指令实现,monitor 对象是同步的基本实现单元
在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头、CAS、Monitor对象,
在 Java 6 的时候,Java 虚拟机提供了三种 monitor 实现:偏向锁(Biased Locking)、轻量级锁和重量级锁,改进了性能。

作用于三个位置:

  1. 作用在静态方法上,则锁是当前类的Class对象。

  2. 作用在普通方法上,则锁是当前的实例(this)。

  3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定锁对象,例如this、Xxx.class。

对象头存储锁信息: synchronized的底层是采用对象头的Mark Word来存储锁信息的。Hotspot 虚拟机(JVM默认虚拟机)中每个对象都有一个对象头(Object Header),包含Mark Word(标记字段) 和 Class Pointer(类型指针)。

  • Mark Word(标记字段):存储哈希码、GC分代年龄、锁信息、GC标记(标志位,标记可达对象或垃圾对象)等。锁信息包括:

    • 锁标志位:64位的二进制数,通过末尾能判断锁状态。01未锁定、01可偏向、00轻量级锁、10重量级锁、11垃圾回收标记

    • 偏向锁线程ID、时间戳等;

    • 轻量级锁的指针:指向锁记录的指针

    • 重量级锁的指针:指向Monitor锁的指针

  • 类型指针:指向它的类元数据的指针,用于找到对象所在的类

不考虑共享资源是类变量等特殊情况的话,有共享资源的多个线程通常都属于同一个对象。

Monitor对象:每个 Java 对象都可以关联一个 Monitor 对象,也称为监视器锁或Monitor锁。Monitor锁用于控制线程对共享资源的访问,开发人员不能直接访问Monitor对象。当一个线程获取了Monitor的锁后,其他试图获取该锁的线程就会被阻塞,直到当前线程释放锁为止。

当一个线程执行synchronized方法或代码块并升级成重量级锁时,当前对象会关联一个Monitor对象,线程须获得该对象的Monitor锁才能执行。Monitor有Owner、EntryList、WaitSet三个字段,分别表示Monitor的持有者线程(获得锁的线程)、阻塞队列、和等待队列

线程通信:synchronized通过Monitor对象,利用Object的wait,notify,notifyAll等方法来实现线程通信。

锁升级:JDK6之前synchronized只有无锁和重量级锁两个状态,JDK6引入偏向锁、轻量级锁两个状态,锁可以根据竞争程度从无锁状态慢慢升级到重量级锁。当竞争小的时候,只需以较小的代价加锁,直到竞争加剧,才使用重量级锁,从而减小了加锁带来的开销。

  • 锁升级顺序:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
  • 无锁:没有线程访问同步代码块时。没有对资源进行锁定,所有的线程都能访问并不断修改同一个资源,但同时只有一个线程能修改成功,失败线程会不断重试。
  • 偏向锁:当有一个线程访问同步代码块时升级为偏向锁。一段同步代码块一直被一个线程所访问,那么该线程id会CAS写入对象头,下次再访问同步代码块时对象头检查到该线程id,就不用加锁解锁了,降低获取锁的代价。
  • 轻量级锁(自旋锁):有锁竞争时升级为轻量级锁。其他线程会通过自旋的形式尝试通过CAS将对象头中Mark Word替换为指向线程栈帧里锁记录的指针,从而获得锁;同时线程锁记录里存放Mark Word信息。竞争的线程不会阻塞但会一直自旋,消耗CPU性能,但速度快。
  • 重量级锁:锁膨胀(自旋失败10次)时升级为重量级锁。Mark Word中存储的是指向Monitor锁的指针,对象Mark Word信息也会保存在Monitor锁里,当一个线程获取了Monitor锁后,竞争线程会被阻塞,不再自旋,不消耗CPU,速度慢。

多线程中 synchronized 锁升级的原理

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

synchronized 和 volatile 的区别

volatile 是变量修饰符;synchronized 是修饰类、方法、代码段
volatile 仅能实现变量的修改可见性但不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞

synchronized 和 ReentrantLock 的区别

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但在 Java 6 中对 synchronized 进行了非常多的改进。主要区别如下:

  • ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
  • ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
  • ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰类、方法、代码块等。

atomic 的原理

atomic 主要利用 CAS (Compare And Wap) 、 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。


Lock锁

Lock提供比同步方法和代码块更广泛的锁定操作。

  • lock():获取锁。如果锁不可用,则当前线程将被禁用,直到获取锁为止。
  • tryLock():尝试获取锁,如果锁可用,则获取并立即返回 true;如果锁不可用,则立即返回 false,不会等待。
  • tryLock(long time, TimeUnit unit):尝试在指定的时间内获取锁。如果锁可用,则获取并立即返回 true;如果在指定时间内锁不可用,则等待直到超时,然后返回 false。
  • unlock():释放锁。
  • newCondition():返回一个绑定到此 Lock 实例的新 Condition 实例,可以用于线程之间的协调等待。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
tickets--;
} else {
break;
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
Thread t1 = new Thread(sellTicket);
Thread t2 = new Thread(sellTicket);
Thread t3 = new Thread(sellTicket);
t1.start();
t2.start();
t3.start();
}
}

synchronized 和 Lock 的区别

Lock和synchronized有以下几点不同:

  • 作用范围。synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁
  • 接口和关键字。Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
  • 死锁问题。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
  • 让等待锁的线程响应中断。Lock可以可以通过lockInterruptibly()获取锁的方法让等待锁的线程响应中断。而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
  • 得知是否成功获取锁。通过Lock可以通过tryLock()知道有没有成功获取锁,而synchronized却无法办到。
  • 性能对比。两者性能差不多。JDK6之前synchronized没有锁升级,线程竞争非常激烈时Lock的性能要远远优于synchronized;而JDK6引入锁升级后,线程竞争激烈时候两者性能也相差无几。

lock锁中断线程:若有线程已拿到锁,其他线程使用lock()获取锁时会阻塞,使用lockInterruptibly()获取锁时会直接中断抛出InterruptedException异常。

lock锁编码习惯:加锁代码要放到try外面。如果放在try里面的话,加锁失败抛异常或者加锁前的代码抛异常后,执行finally里的解锁代码,而其实加锁都没成功,最终解锁就也不合适了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lock.lock();         // 加锁
try{
// do something
}finally{
lock.unlock(); // 解锁
}

// 不推荐
try{
int a=3/0; // 这里抛异常会直接进入finally
lock.lock(); // 加锁
// do something
}finally{
lock.unlock(); // 解锁
}

分布式锁:SETNX、Redisson。Redisson基于Redis协议,可以实现可重入锁、公平锁、读写锁、信号量、闭锁(计数器),支持看门狗自动续期。

反射

基本介绍

反射:在程序运行期间动态地获取类的信息并对类进行操作的机制。

通过反射机制可以实现:

  • 获取类或对象的Class对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用setAccessible(true)方法开启私有权限)。注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。

  • 创建实例:程序运行时,可以利用反射先创建类的Class对象再创建该类的实例,并访问该实例的成员;Xxx.class.newInstance() ;例如在Spring容器类源码里,Bean实例化就是通过Bean类的Class对象。Bean类的Class对象是从BeanDefinition对象的type成员变量取的。BeanDefinition对象存储一些Bean的类型、名称、作用域等声明信息。

  • 生成动态代理类或对象:程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。例如JDK中Proxy类的newProxyInstance静态方法,可以通过它创建基于接口的动态代理对象。

类的字节码文件和Class对象的区别:

  • 类的class字节码文件是编译时生成的,类的class对象是运行时生成的。
  • 类的字节码文件是存储在电脑硬盘中的文件,如Test.class;类的Class对象是存放在内存中的数据,可快速获取其中的信息;
  • 两者都存储类的各种信息;

获取类Class对象的JVM底层:如果该类没有被加载过,会首先通过JVM实现类的加载过程,即加载、链接(验证、准备、解析)、初始化,加载阶段会生成类的Class对象。

获取类Class对象的方法:dog.getClass();Dog.class;Class.forName("package1.Dog");

特点:

  • 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
  • 越过泛型检查:反射可以越过泛型检查,例如在ArrayList中添加字符串

反射的优缺点:

  • 优点
    • 运行时获取属性:运行期间能够动态的获取类,提高代码的灵活性。
    • 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
    • 越过泛型检查:反射可以越过泛型检查,例如在ArrayList中添加字符串
  • 缺点:性能差。性能比直接的Java代码要差很多。

应用场景:

  • JDBC加载数据库的驱动:使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
  • Bean的生命周期:
    • 实例化xml解析出的类:多数框架都支持注解或XML配置来定义应用程序中的类,从xml配置中解析出来的类是字符串,需要利用反射机制实例化;如Spring通过<bean id="xx" class="类全限定名"><property name="按名称注入" ref="被注入Bean的id">定义bean,然后通过Class.forName("xxx.Xxx")获取类的class对象,然后创建实例。
    • 注解容器类加载Bean、实例化Bean:Bean的生命周期中,注解容器类的构造方法会遍历@ComponentScan("扫描路径")下的.class文件,通过类加载器.load("类名")方式获得类的class对象,存入beanDefinitionMap。然后遍历beanDefinitionMap,通过class对象实例化等。
  • AOP创建动态代理对象:面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理对象,这必须由反射机制来实现。

验证反射可以绕过泛型检查:

基于反射,我们可以给ArrayList<Integer>对象中,加入字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {

public static void main(String[] args) throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
ArrayList<Integer> integers = new ArrayList<>();
Class<? extends ArrayList> aClass = integers.getClass();
Method add = aClass.getMethod("add", Object.class);
add.invoke(integers, 1);
add.invoke(integers, 2);
add.invoke(integers, 3);
add.invoke(integers, 4);
add.invoke(integers, "hello");
System.out.println(integers);
}
}
// [1,2,3,4,hello]

反射获取Class对象

基本介绍

Class类的对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用setAccessible(true)方法开启私有权限)。

注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。

获取类Class对象:

  • 对象.getClass():Object是一切类的根类,Object类有个getClass()方法可以获取类的Class对象。例如dog.getClass();
  • 类名.class(推荐):例如Dog.class;
  • Class.forName(“类名”):例如Class.forName(“package1.Dog”);

Class对象的常用方法:

  • 获取类的信息
    • String getName():返回类的全限定名。全限定名包含包名和类名,用于唯一标识类或接口。例如package1.Dog、java.lang.String、java.util.Map$Entry
    • String getSimpleName():返回类的简单名。例如Dog
    • tring getCanonicalName():返回类的规范名。规范名是类的规范字符串形式,常用于打印和日志记录。例如package1.Dog、java.lang.String、java.util.Map.Entry
    • Package getPackage():返回此类所属的包。
    • ClassLoader getClassLoader():返回该类的类加载器。
    • Class<? super T> getSuperclass():返回表示类的超类的 Class 对象。
    • Class<?>[] getInterfaces():返回类实现的所有接口。
    • boolean isInterface():判断是否是接口。
    • boolean isAnnotation():判断是否是注解类型。
    • boolean isEnum():判断是否是枚举类型。
    • Annotation[] getAnnotations():返回此元素上存在的所有注解。
    • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。
    • T getAnnotation(Class annotationClass):返回指定类型的注解,如果该注解存在于此元素上,否则返回 null。例如Spring源码中,ApplicaitonContext构造器判断一个类是不是Bean,是通过这个方法判断类有没有@Comonent等注解,从而判断它是不是Bean。
  • 获取成员:
    • Field[] getFields():返回类的所有公共字段,包括从父类继承的字段。
    • Field[] getDeclaredFields():返回类声明的所有字段,不包括继承的字段。
    • Method[] getMethods():返回类的所有公共方法,包括从父类继承的方法。
    • Method[] getDeclaredMethods():返回类声明的所有方法,不包括继承的方法。
    • Constructor<?>[] getConstructors():返回类的所有公共构造方法。
    • Constructor<?>[] getDeclaredConstructors():返回类声明的所有构造方法。
  • 其他方法
    • T newInstance():创建此 Class 对象所表示的类的一个新实例(使用默认构造方法)。

Spring源码:Bean初始化时判断类是否Bean、判断属性是否需要填充都用到了反射

Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 狗类
public class Dog {
private int weight;
public String name;
// 其他定义
}
// 获取类的Class对象:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//方法1:类的class属性
Class<Dog> c1=Dog.class;
//方法2:对象的getClass方法
Dog wangCaiDog = new Dog(23, "旺财");
Class<? extends Dog> c2= wangCaiDog.getClass();
//方法3:Class类的静态方法forName
Class<?> c3= Class.forName("package1.Dog");
// 方法4:使用类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Class<?> c5 = systemClassLoader.loadClass("package1.Dog");
// 输出:class package1.Dog
System.out.println(c1);
//三种方式获取到Class对象地址是完全一致的
// 输出:true
System.out.println(c1==c2&&c1==c3);
}
}
// 获取类的信息
public class Test {
public static void main(String[] args) throws Exceptionn {
Class<? extends Dog> dogClass = Dog.class;
System.out.println(dogClass.getName());
System.out.println(dogClass.getSimpleName());
System.out.println(dogClass.getCanonicalName());
}
}

全限定名和规范名

全限定名和规范名:

外部类的全限定名和规范名是一样的,都是“xxx.类名”。区别主要在内部类,内部类的全限定名是“xxx.外部类名$内部类名”,规范名是“xxx.外部类名.内部类名”。

  • 简单名:只包含类名。例如Dog、String、Entry
  • 全限定名:包含包名和类名,用于唯一标识类或接口,通过全限定名能找到唯一一个类。例如package1.Dog、java.lang.String、java.util.Map$Entry
  • 规范名:类的规范字符串形式,常用于打印和日志记录。例如package1.Dog、java.lang.String、java.util.Map.Entry
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {
// java.util.Map
System.out.println(Map.class.getName());
// java.util.Map
System.out.println(Map.class.getCanonicalName());
// 输出 "java.util.Map$Entry"
System.out.println(Map.Entry.class.getName());
// 输出 "java.util.Map.Entry"
System.out.println(Map.Entry.class.getCanonicalName());
}

反射获取成员

反射获取构造方法

Class对象获取构造器:

  • getConstructor(Class<?>… parameterTypes):获取指定参数类型的公共构造方法。返回值是Constructor类
  • getDeclaredConstructor(Class<?>… parameterTypes):获取指定参数类型的构造方法(包括私有构造方法)。
  • getConstructors():获取所有公共构造方法。
  • getDeclaredConstructors():获取所有构造方法(包括私有构造方法)。
  • **newInstance()**:创建类的新实例。
    • Class类的newInstance():只能够调用无参构造函数;
    • Constructor类的newInstance():可以根据传入的参数,调用任意构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 获取所有构造器对象:
public static void main(String[] args) {
Class<Dog> dogClass = Dog.class;
Constructor<?>[] cons = dogClass.getDeclaredConstructors();
for(Constructor<?> con:cons){
System.out.println(con);
}
}
// 获取单个构造器并实例化:
public class Test {
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
Class<Dog> dogClass=Dog.class;
//获取单个构造方法对象
Constructor<?> con=dogClass.getDeclaredConstructor();
//构造方法对象实例化,会调用无参构造方法
Object dogObject = con.newInstance();
// 无参构造器实例化,也可以直接用Class对象的newInstance方法,带参就不行了
Dog dog = dogClass.newInstance();
//重写了Dog类的to_String,所以输出:Dog{weight=0, name='null'}
System.out.println(dogObject);
System.out.println(dog);
}
}

反射获取字段

Class对象获取字段:

  • getField(String name):返回指定名称的公共字段。返回类型是字段类Field。
  • getDeclaredField(String name):返回指定名称的字段(包括私有字段)
  • getFields():返回所有公共字段。
  • getDeclaredFields():返回所有字段(包括私有字段)

字段类Field常用方法:

获取字段信息

  • getName():返回字段的名称。
  • getType():返回字段的类型。
  • getModifiers():返回字段的修饰符。
  • getDeclaringClass():返回声明该字段的类的 Class 对象。

获取和设置字段值

  • get(Object obj):返回指定对象上此字段的值。
  • getBoolean(Object obj):返回指定对象上此字段的值(如果字段类型是 boolean)。
  • getByte(Object obj):返回指定对象上此字段的值(如果字段类型是 byte)。
  • getChar(Object obj):返回指定对象上此字段的值(如果字段类型是 char)。
  • getDouble(Object obj):返回指定对象上此字段的值(如果字段类型是 double)。
  • getFloat(Object obj):返回指定对象上此字段的值(如果字段类型是 float)。
  • getInt(Object obj):返回指定对象上此字段的值(如果字段类型是 int)。
  • getLong(Object obj):返回指定对象上此字段的值(如果字段类型是 long)。
  • getShort(Object obj):返回指定对象上此字段的值(如果字段类型是 short)。
  • set(Object obj, Object value):设置指定对象上此字段的值。注意私有字段默认不允许赋值,要赋值必须给私有字段setAccessible(true)。
  • setBoolean(Object obj, boolean value):设置指定对象上此字段的值(如果字段类型是 boolean)。
  • setByte(Object obj, byte value):设置指定对象上此字段的值(如果字段类型是 byte)。
  • setChar(Object obj, char value):设置指定对象上此字段的值(如果字段类型是 char)。
  • setDouble(Object obj, double value):设置指定对象上此字段的值(如果字段类型是 double)。
  • setFloat(Object obj, float value):设置指定对象上此字段的值(如果字段类型是 float)。
  • setInt(Object obj, int value):设置指定对象上此字段的值(如果字段类型是 int)。
  • setLong(Object obj, long value):设置指定对象上此字段的值(如果字段类型是 long)。
  • setShort(Object obj, short value):设置指定对象上此字段的值(如果字段类型是 short)。

其他方法

  • isAccessible():返回字段是否可访问。
  • setAccessible(boolean flag):设置字段的可访问性。通过这个方法可以让私有字段也可以赋值。
  • oGenericString():返回字段的描述,包括泛型信息。
  • getAnnotatedType():返回此字段的带注释的类型。
  • getAnnotations():返回字段的所有注解。
  • getAnnotation(Class annotationClass):返回字段的指定类型的注解,如果该注解不存在,则返回 null。例如Spring源码中依赖注入这一块,就是基于反射获取类中字段有没有@Resource、@Component等注解,有的话就是要注入Bean.
  • getDeclaredAnnotations():返回直接存在于此字段上的所有注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 获取成员变量对象并赋值
public class Test {
public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException {
// 1.获取Class对象,并实例化
Class<? extends Dog> dogClass = Dog.class;
Dog dog = dogClass.newInstance();
// 2.获取字段对象
Field nameField = dogClass.getDeclaredField("name");
Field weightField = dogClass.getDeclaredField("weight");
// 3.给字段对象赋值
nameField.set(dog, "旺财");
// 注意私有字段默认不允许赋值,要赋值必须给私有字段设置可访问
weightField.setAccessible(true);
weightField.set(dog, 10);
System.out.println(dog);
}
}
// 通过Field获取的Class类:
public class Test {
public static void main(String[] args) throws NoSuchFieldException {
Class<Dog> dogClass=Dog.class;
Field weightField = dogClass.getDeclaredField("name");
Class<?> dogClassByField = weightField.getDeclaringClass();
// 通过字段获取到的class对象和源class对象是地址是一样的,事实上一个类的所有Class对象都是一个实例
// true
System.out.println(dogClassByField==dogClass);
}
}

反射获取普通方法

Class对象获取成员方法的方法:

  • getMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的公共方法。返回值是方法类Method。

  • getDeclaredMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的方法(包括私有方法)。

  • getMethods():返回所有公共方法(包括从父类继承的方法)。

  • getDeclaredMethods():返回所有方法(包括私有方法)。

  • Method类的方法:

    • 获取方法信息
      • getName():返回方法的名称。
      • getReturnType():返回方法的返回类型。
      • getParameterTypes():返回方法参数类型的数组。
      • getModifiers():返回方法的修饰符。
      • getDeclaringClass():返回声明此方法的类的 Class 对象。
  • 调用方法

    • Object invoke(Object obj, Object… args):调用指定对象上此 Method 对象表示的基础方法。
  • 其他方法

    • isAccessible():返回方法是否可访问。
    • setAccessible(boolean flag):设置方法的可访问性。
    • getAnnotations():返回此方法的所有注解。例如Spring源码中通过此方法判断一个类中
    • isAnnotationPresent(Class<? extends Annotation> annotationClass):判断此方法是否被指定的注解类型注释。
    • getAnnotation(Class annotationClass):返回该方法的指定类型的注解。
    • getExceptionTypes():返回此方法抛出的异常类型的数组。
    • toGenericString():返回方法的描述,包括泛型信息。

获取成员变量对象并调用:

1
2
3
4
5
6
7
8
9
10
11
12
// 1.获取构造方法对象并实例化
Class<?> c= Class.forName("train.Dog");
Constructor<?> con=c.getConstructor();
Object obj = con.newInstance();
// 2.获取成员方法对象
Method eat = c.getMethod("eat");
// 3.通过成员方法对象的invoke方法,调用构造方法对象的成员方法
// 无参无返回值方法
eat.invoke(obj);
// 带参有返回值方法
Object sucess= eat.invoke(obj,"food");
System.out.println((boolean)sucess);