Java中,最常用的就是通过new调用相应构造器来创建对象实例,而当构造器不是public,而是private,new没了用武之地,我们又该怎样创建对象实例?先创建构造器被private修饰的类,代码如下:
@Setter
@Getter
public class Girlfriend {
private String name;
private int age;
private Girlfriend(String name) {
this.name = name;
}
}
让我最后再new一遍......
类加载触发时机曾云:除了new,我还有反射。通过反射,可以获取类的字段、方法,同样可以获取类的构造器来创建对象。对于private修饰的字段、方法、类构造器,我们必须禁用安全检查才能获取到。
默认是开启安全检查机制,对于被private修饰的方法、类、构造器会禁止访问。
运行结果:
通过setAccessible(true)来关闭安全检查,访问private修饰的构造器。
运行结果:
就这,就这?其实这篇文章真的想讲的是Unsafe,一个可以直接操作内存,不用构造器就可以创建对象的类。
jvm的出现,让Java不再有C语言管理内存的困扰,同时也失去了类似指针操作内存的功能。于是Unsafe的出现填补了空缺。但正如其名,直接操作内存被认为是不安全的,会带来很多安全问题。所以,Unsafe没法通过new实例化,唯一一个构造器也是private。我们查看一下源码中和Unsafe实例有关的字段、方法。
源码如下:
public final class Unsafe {
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 检查调用类的加载器是不是Bootstrap,也就是null
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
不难看出getUnsafe()是一个public方法,但是它会检查调用getUsafe()类的加载器是不是Bootstrap类加载器,但是我们定义类的默认加载器是AppClassLoader,所以会直接抛出异常。
检查类加载器代码如下:
// bootstrap加载器负责加载rt.jar,不是java编写,所以是null
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
那么,我们只能通过反射从theUnsafe字段和构造器来创建实例。
代码如下:
public class Boy {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class<Unsafe> unsafeClass = Unsafe.class;
// 第一种方式:通过构造器获取Unsafe实例
Constructor<Unsafe> declaredConstructor = unsafeClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe unsafe1 = declaredConstructor.newInstance();
// 第二种方法:通过字段获取Unsafe实例
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe2 = (Unsafe)theUnsafe.get(null);
}
}
利用unsafe实例,使用这个方法可以直接不通过构造器来创建实例对象。
// 利用Unsafe实例构造GirlFriends对象
Girlfriend girlfriend1 = (Girlfriend) unsafe1.allocateInstance(Girlfriend.class);
Girlfriend girlfriend2 = (Girlfriend) unsafe2.allocateInstance(Girlfriend.class);
girlfriend1.setName("小芳1");
girlfriend2.setName("小芳2");
System.out.println(girlfriend1.getName());
System.out.println(girlfriend2.getName());
运行结果:
从运行结果可以看出来,两种方式获取的unsafe对象都能用来创建实例对象。那么Unsafe还有什么其他的功能。现在Unsafe一共提供了100多个方法,现在就找几个来测试一下。
Field nameField = Girlfriend.class.getDeclaredField("name");
System.out.println(girlfriend1.getName());
// 获取name成员变量在内存中的地址相对于对象内存地址的偏移量
long l = unsafe1.objectFieldOffset(nameField);
// 修改成员变量name的值
unsafe1.putObject(girlfriend1, l, "小红");
System.out.println(girlfriend1.getName());
运行结果:
有兴趣的可以了解一下锁里面的CAS原理
Field ageField = Girlfriend.class.getDeclaredField("age");
long l1 = unsafe1.objectFieldOffset(ageField);
girlfriend1.setAge(20);
System.out.println("初始年龄: " + girlfriend1.getAge());
// cas操作,第三个参数必须是旧值,即20,否则修改失败。
unsafe1.compareAndSwapInt(girlfriend1, l1, 20, 18);
System.out.println("修改后年龄: " +girlfriend1.getAge());
// 通过偏移量获取int值
int i = unsafe1.getInt(girlfriend1, l1);
System.out.println("偏移量获取int值:" + i);
运行结果:
// 分配一个8byte的内存,并返回入口地址
long address = unsafe1.allocateMemory(8L);
// 用0000 0000代表一个字节,从入口地址初始化8个字节
unsafe1.setMemory(address, 8L, (byte) 0);
// aLong为0
long aLong1 = unsafe1.getLong(address);
// 修改以address为入口的long类型数据(8byte)
unsafe1.putLong(address,100L);
// aLong2为100
long aLong2 = unsafe1.getLong(address);
Unsafe还要其他的方法,有兴趣可以探究一下。其核心方法主要是围绕着内存地址入口、地址偏移量来展开,在开发中还请慎用。