反射
在Java领域中,反射是一个很强大有很复杂的机制。反射定义: 能够分析类能力的程序称为反射。
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
反射的核心类Class类(类对象)。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
/**
* 反射的三种方式:
* 1、 Class clazz = People.class;
* 2、 Class clazz2 = Class.forName("People");
* 3、 People p2 = new People();
* Class clazz3 = p2.getClass();
*/
// 反射获取类对象
Class clazz = People.class;
// 反射实例化
People p = (People) clazz.newInstance();
// 获取所有属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 设置属性可修改
field.setAccessible(true);
System.out.println(field.getName() + " : " + field.get(p)); // a : 10
// 修改属性的值(破坏封装性private修饰的值也可被修改)
field.set(p, 20);
System.out.println(field.getName() + " : " + field.get(p)); // a : 20
}
// 获取所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
method.setAccessible(true);
// 方法执行,私有和公有都能获取并执行。
System.out.println(method.getName() + " : " + method.invoke(p, null));
//test : 父类方法test()
//pri : null
//getStr : hello
}
//获取无参构造函数
Constructor con = clazz.getDeclaredConstructor();
//通过无参Constructor对象创建Person类的新实例
Object obj = con.newInstance();
System.out.println(obj); // People@79b4d0f
}
}
class People {
private int a = 10;
private void pri() {
}
public String test() {
return "父类方法test()";
}
public String getStr() {
return "hello";
}
}
反射会破坏封装性,同时也会破坏单例模式。
public static void main(String[] args) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
Singleton singleton = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
System.out.println("singleton的hash值: " + System.identityHashCode(singleton) + " singleton2的hash值: "
+ System.identityHashCode(singleton2) + " 是否同一个对象: " + (singleton2 == singleton));
//输出结果: singleton的hash值: 93122545 singleton2的hash值: 93122545 是否同一个对象: true
Class clazz = Singleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton3 = (Singleton) constructor.newInstance();
System.out.println("singleton的hash值: " + System.identityHashCode(singleton) + " singleton3的hash值: "
+ System.identityHashCode(singleton3) + " 是否同一个对象: " + (singleton3 == singleton));
//输出结果: singleton的hash值: 93122545 singleton3的hash值: 403424356 是否同一个对象: false
}
}
/**
* 双重校验锁实现的单例模式
*/
class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
防止单例被破坏的实现原理,反射破坏单例模式重点在于反射能够获取到单例的构造方法,并且调用构造方法构建对象。这种方式的防范可以在构造方法中加入校验:
/**
* 双重校验锁实现的单例模式
*/
class Singleton {
private volatile static Singleton singleton;
private Singleton (){
if(singleton != null){
throw new RuntimeException("禁止反射调用构造方法创建对象");
}
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
实现的效果如下:
但是这种方式有一个重要前提,反射机制不能优先创建对象,否则还是会失效:
public static void main(String[] args) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
Class clazz = Singleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton3 = (Singleton) constructor.newInstance();
Singleton singleton = Singleton.getSingleton();
System.out.println("singleton的hash值: " + System.identityHashCode(singleton) + " singleton3的hash值: "
+ System.identityHashCode(singleton3) + " 是否同一个对象: " + (singleton3 == singleton));
//输出结果: singleton的hash值: 93122545 singleton3的hash值: 2083562754 是否同一个对象: false
}
枚举
比较完美的解决单例模式被反射破坏的一种方式是采用枚举方式。
public class Main {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
SingletonEnum singleton = SingletonEnum.INSTANCE;
SingletonEnum singleton2 = SingletonEnum.INSTANCE;
System.out.println("singleton的hash值: " + System.identityHashCode(singleton) + " singleton2的hash值: "
+ System.identityHashCode(singleton2) + " 是否同一个对象: " + (singleton2 == singleton));
// 输出结果: singleton的hash值: 93122545 singleton2的hash值: 93122545 是否同一个对象: true
Class clazz = SingletonEnum.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonEnum singleton3 = (SingletonEnum) constructor.newInstance();
System.out.println("singleton的hash值: " + System.identityHashCode(singleton) + " singleton3的hash值: "
+ System.identityHashCode(singleton3) + " 是否同一个对象: " + (singleton3 == singleton));
// 抛出异常
}
}
enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() {
return INSTANCE;
}
}
与创建对象的顺序无关:
public class Main {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
Class clazz = SingletonEnum.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonEnum singleton3 = (SingletonEnum) constructor.newInstance();
SingletonEnum singleton = SingletonEnum.INSTANCE;
System.out.println("singleton的hash值: " + System.identityHashCode(singleton) + " singleton3的hash值: "
+ System.identityHashCode(singleton3) + " 是否同一个对象: " + (singleton3 == singleton));
// 输出结果: singleton的hash值: 93122545 singleton2的hash值: 93122545 是否同一个对象: true
}
}
enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() {
return INSTANCE;
}
}
执行结果: