1.使用反射改变自动装箱后的值
2.改变String的不可变性
3.破坏泛型的约束
4.破坏单例
4.1.反射调用已有的构造函数
4.2.添加一个构造函数
4.3.使用Unsafe创建实例
关于反射的知识点大家应该已经知道,这里就不多说了。
1.使用反射改变自动装箱后的值
对于基本数据类型都有对应的包装类,jdk提供了自动装箱和拆箱的功能,当一个int类型的数据装箱成Integer时实际上是调用了Integer.valueOf(int i)方法:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
也就是说对于-128到IntegerCache.high的int值在自动装箱时是从IntegerCache.cache里拿到的事先创建好的Integer对象,那么我们可以改变这个cache从而改变自动装箱后的值:
public static void main(String[] args) throws Exception { //获取IntegerCache类中的cache字段 Class integerCache = Class.forName("java.lang.Integer$IntegerCache"); Field cacheField = integerCache.getDeclaredField("cache"); cacheField.setAccessible(true); //得到实际的cache Integer[] cache = (Integer[]) cacheField.get(null); Integer integer = -1; for (int i = 0; i < cache.length; i++) {//改变cache的内容 cache[i] = integer; } for (int i = -128; i <= 127; i++) { System.out.print((Integer)i); } }
上面这段代码打印的值对于每个i来说都是-1。
2.改变String的不可变性
大部分人都会告诉你String对象是不可变的,因为它定义为final的,而且内部大部分字段也是final的,但是String对象确实是可以变的,先看一下它的定义:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; …… }
对于String str = “abcd”来说,实际上是在常量池中存在一个字面量为"abcd"的String对象,而"abcd"是存储在private final char value[];这个字符数组里面的,所以我们可以拿到这个char[],然后对其进行修改:
public static void main(String[] args) throws Exception { final String str1 = "abcd"; final String str2 = new String("abcd"); System.out.println(str2 == str1); Field valueField = String.class.getDeclaredField("value");//获取私有字段value valueField.setAccessible(true);//设置访问权限 char[] value = (char[]) valueField.get(str1);//得到str1所引用对象的value字段的值 value[0] = '0'; value[1] = '1'; value[2] = '2'; value[3] = '3'; System.out.println(str1); System.out.println(str2); }
这里输出的是0123而不是abcd,对于刚创建的str2来说,它是一个引用变量指向内存中的一个字面量也是"abcd"的String对象,只是这个String对象实际上和str1所指向的String对象共用了同一个字符数组,所以当str1所指向的String对象里的字符数组发生改变时str2所指向的String对象的值也发生了改变。
3.破坏泛型的约束
对于List<String> list = new ArrayList<String>();来说,因为加了泛型约束,list.add(..)的时候你只能加入String类型的对象,而通过反射获取add方法后就可以加入你想加入的类型了:
public static void main(String[] args) throws Exception { List<String> list = new ArrayList<String>(); //得到add方法 Method method = List.class.getMethod("add", Object.class); //放入一个Integer类型的对象 method.invoke(list, new Integer(1)); System.out.println(list.size()); }
4.破坏单例
4.1.反射调用已有的构造函数
单例的目的是为了内存中只能创建一个实例,它的实现有很多种:懒汉、饿汉、DCL、静态内部类、单个枚举等,不论哪种方式都是可以破坏它只能创建一个实例的目的,以静态内部类为例:
public class Singleton { private Singleton(){ } public static Singleton getInstance() { return InstanceHolder.instance; } private static class InstanceHolder { private static Singleton instance = new Singleton(); } }
单例的一个重要的约束是构造函数私有化,不过我们通过反射还是可以创建对象的:
public static void main(String[] args) throws Exception { //得到构造函数 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); //设置访问权限 constructor.setAccessible(true); //创建对象 Singleton singleton = constructor.newInstance(null); System.out.println(singleton == Singleton.getInstance()); }
大家都知道使用已有的构造函数可以创建对象,其实不使用已有的构造函数也是可以创建对象的,这里介绍4.2、4.3两种:
4.2.添加一个构造函数
不使用已有的构造函数,给类新添加一个构造函数用来创建对象:
public static void main(String[] args) throws Exception { //得到Object的无参构造函数 Constructor javaLangObjectConstructor = Object.class.getConstructor(); //给Singleton新加一个构造函数 Constructor c = ReflectionFactory.getReflectionFactory() .newConstructorForSerialization(Singleton.class, javaLangObjectConstructor); c.setAccessible(true); //创建实例 Singleton singleton = (Singleton) c.newInstance(null); System.out.println(singleton == Singleton.getInstance()); }
4.3.使用Unsafe创建实例
Unsafe中提供了一个allocateInstance方法可以用来创建对象,Unsafe其实是一个单例,只是提供的获取Unsafe的方法中有安全监测致使我们不能通过它拿到内部的Unsafe对象,不过我们可以通过反射拿到内部的theUnsafe字段,进而得到已创建好的Unsafe对象:
public static void main(String[] args) throws Exception { //通过Unsafe创建对象 Singleton allocateInstance = (Singleton) getUnsafe().allocateInstance(Singleton.class); System.out.println(allocateInstance == Singleton.getInstance()); } //通过反射拿到Unsafe对象 private static Unsafe getUnsafe() throws Exception { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return (Unsafe)f.get(null); }
本文例子中使用的jdk版本是hotspot 1.6,文中提到的几个例子在实际开发中可能用不到,但是对学习java还是有帮助的。