我们先来看一段代码:
public static void main(String[] args) {
if(Boolean.valueOf("true") == false)
System.out.println("true == false");
}
你觉得这段代码的输出是什么?1. 什么也没有。 2. true == false. 3. 这可不好说。
选1的都是好孩子,直接选3的就请不要往下看了。选2的,只能说,你们太配合了。
其实这是源于stackoverflow很久前的一个帖子,前几天和同事正好聊起到。在我的机器上,这段代码是输出true== false的,这是因为我没把代码粘贴全,其实完整的代码是这样的:
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test {
static {
try {
Field f = Boolean.class.getDeclaredField("TRUE");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setAccessible(true);
f.set(Boolean.class, Boolean.FALSE);
}
catch(Exception ex) {}
}
public static void main(String[] args) {
if(Boolean.valueOf("true") == false)
System.out.println("true == false");
}
}
Boolean类内部有这么两个静态成员变量:
/**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>true</code>.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);
当调用Boolean.valueOf()或者触发编译器的自动装箱的时候,都会用到这两个变量。上面的代码里我们就是通过反射将其中的那个TRUE修改成了new Boolean(false),因此能会输出"true==false”。注意这个字段是final类型的,所以简单的.setAccessible(true);它可不吃这一套,因此后面会将它的修饰符改成非final的: modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);这里稍微有点饶,这个修饰符是在TRUE对应的这个静态成员变量的Field实例(Boolean.class.getDeclaredField("TRUE"))的一个叫modifier的字段里(Field.class.getDeclaredField("modifiers");)。
当然,如果你看到你的员工写出Boolean.valueOf("true") == false这样的语句的话,你估计第一反应就是把他给炒了。但是有的情况就不那么容易发现了。我们经常会把数据序列化成XML或者JSON的格式,当然我们同样也需要将它们进行反序列化。
假设有这么一段JSON:
{ "deepinmind" : true }
你可以用fastjson之类的对它进行反序列化,
public static void main(String[] args) {
JSONObject json = (JSONObject)JSON.parse("{ \"deepinmind\" : true }") ;
System.out.println("deepinmind: "+json.getBoolean("deepinmind"));
}
结果是:
deepinmind: false
因为fastjson会把解析后的值存到一个map里,这样的话就正好触发了自动装箱。在不知不觉中,你的结果已经被修改了。
当然能做的远不止这些,比如下面这段代码:
public class Test {
static {
try {
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
Class<?> cls = Class.forName("java.lang.Integer$IntegerCache") ;
Field f = cls.getDeclaredField("cache");
f.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
Integer[] array = (Integer[])f.get(cls);
array[128+21] = new Integer(22);
}
catch(Exception ex) {}
}
public static void main(String[] args) {
System.out.format("3 * 7 = %d", 21);
}
}
3 * 7 = 22
看吧,再也不用管什么三七二十一了。
这是因为Integer会把-128~127之间的整型缓存起来,自动装箱的时候会优先使用缓存的这些对象。而System.out.format后面是个变长参数,这会触发int类型的自动装箱。前面之所以加了128是因为cache数组的前128位存的是负数:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
想得到这个输出,你除了可以修改IntegerCache外,还可以自己写一个PrintStream,把System.out给替换掉,这样你想输出什么就能输出什么了。不过这个也得用到反射,因为System.out是final类型的。
当然了,权当娱乐,可千万不要这么写代码。如果它让你感到害怕,你可以看下
这个讨论,了解下如何把这个功能给屏蔽掉。
原创文章转载请注明出处:
http://it.deepinmind.com
分享到:
相关推荐
在Java中,注释有两种形式:单行注释(`//`)和多行注释(`/* */`)。`c`被认为是一个非法注释,因为它既不是以`//`开头的单行注释也不是以`/*`开头的多行注释。 **1.3 类路径问题** 当Java解释器无法找到指定的类时,...
在Java中,函数常常与方法的概念相结合,即类中的函数称为方法。 - **数组与列表**:数组是一种数据结构,可以存储固定大小的顺序集合。在Java中,数组的长度在创建时确定,之后不可改变。 - **循环递推与递归**:...
题目中的正确答案是:“与全1的二进制数进行按位异或之后每一个数字都会颠倒。” ### 8. 控制语句 **知识点**:中断控制语句的工作原理。 **详细解释**:题目中提到了`break`、`continue`、`return`等控制语句。...
2. **String 不是最基本的数据类型**:Java 中的基本数据类型包括 byte、short、int、long、float、double、char 和 boolean,而 String 是一个对象,属于引用类型。 3. **String 对象创建**:`String s = new ...
- **基本数据类型:** Java中的`int`、`char`、`byte`等基本数据类型及其范围。 - **类型转换:** 字符可以被自动转换为整数,整数也可以被显式或隐式地转换为字符。 ### 3. 方法覆盖与返回类型 此题考察了Java中...
Java实现单向链表反转是指将单向链表的顺序颠倒,例如原链表为A->B->C->D->E->F,反转后变为F->E->D->C->B->A。这种操作在实际开发中非常有用,例如在数据处理、数据分析等领域。 单向链表反转的实现方式有两种,一...
线程的执行顺序是非确定性的,因此正确答案是D("run."先打印,然后是"java.lang.RuntimeException: Problem")和E(两个输出可以按任何顺序出现,但都必须包含)。这表明考生需要理解线程并发执行的特点,包括异常...
前后顺序可以任意颠倒, 不影响库中的数据关系**。在关系数据库中,记录的具体物理顺序对数据的关系没有影响。只要每个记录的字段值正确,记录的顺序是可以随意调整的。 #### 7. 需求分析阶段的主要文档 - **知识...
• sample04.htm 在同一个网页中使用不同脚本语言 • sample05.htm 判断浏览器对JavaScript版本的支持情况 • sample06.htm 引用外部JavaScript • sample07.htm 在同一个HTML文档...