文档章节

关于2+2=5的问题

于忠达
 于忠达
发布于 2014/06/06 10:32
字数 1381
阅读 129
收藏 0

在“如何编程实现2+2=5”一文中讨论了一个可行的方案,窃以为众人的焦点都被转移了。原文见http://www.oschina.net/news/52412/write-a-program-that-makes-2-2-5
1. 文中的纰漏问题

Integer a = new Integer(2);
Integer b = new Integer(2);
System.out.print(a == b);

这个在java中输出必然是false,因为两个地址是不一样的,两边类型对等都是对象时,==比较的是地址,而不是值。如果有一侧是原生类型,则尝试把另一边转换成为原生类型来进行比较。把基本类型封装拆成原始类型值的隐式转换,这就从JDK1.5开始引入的自动拆包(拆箱)。至于有评论说是true,应该是没有亲自去试试,或者不熟悉源码。new是产生一个新的对象,不是工厂模式,去取缓存中一个已经存在的。

2. 巧妙的障眼法

不觉得文中使用的打印方法有点不正常么?一般人打印的时候我想都是这样的:

System.out.println("2+2="+(2+2));

而文中给出的打印方式为:

System.out.printf("%d",2 + 2);

这是一个典型的障眼法,也是配合他的思想的。

printf这个方法其实在C中是常用的,在Java中的声明是这样的:

    public PrintStream printf(String format, Object ... args)

这里给我们看到,参数是一个变长的数组类型,元素的类型是Object

2+2得到的是一个原生的数字类型4,并且这个结果不会变的。但原生类型4怎样转换成为Object呢?由于4是原始类型,对应的包装类型是Integer,因此首先自动装箱成为Integer,而这个过程是调用Integer.valueOf()方法来完成。谁来调用的呢?编译器。因此Java语法中许多的操作都是编译器来做繁琐的处理,而不是语法真的能实现这个功能。比如字符串和数字的相加,并非+有这样的能力,而是编译器来完成语法的解析和执行。+就只做数字的运算。

所以调用printf的过程中,原生的数字4被调用Integer.valueOf(4)来转换成为Object,这正中下怀,从Integer.valueOf的实现来看,通过修改缓存的数字序列,的确达到了移花接木的效果。如果不是使用printf,而使用print或者println,你都得不到这个结果。

3. 反射操作的技巧

在这个方法中,真正的技巧和难度其实不在于修改cache序列中的引用,当然这是一个绝佳的点子,并且很好的应用了自动装箱这个方式完成了从里面的取值,但真正应该学习的技巧却是关于反射的使用,尤其是这几句:

        Class cache = Integer.class.getDeclaredClasses()[0];
        Field c = cache.getDeclaredField("cache");
        c.setAccessible(true);
        Integer[] array = (Integer[]) c.get(cache);

我估计学过Java的人可能大多只是知道这几句是什么意思,但也只是看着代码去解释而已,如果让你自己写,八成是写不出来的。

第一句是获取Java类中所定义的内部类,并且取了其中的第一个。这个只有读过源码才知道,Integer里面也只定义了一个静态内部类,就是用来做存储的,存储的成员变量名字叫cache,也是静态的。因此这里写死了取第一个([0])。

第二句是从这个类中取定义的成员变量(field)。由于这个成员变量是外部不可访问的(包级访问权限),因此第三句c.setAccessible(true)用来通知JVM当这个方法或成员变量被调用的时候,不要进行可访问性的检查(参见AccessableObject.java的setAccessible的JavaDoc)。然后就可以顺利的从cache这个类中取出其成员成员变量cache,其类型是Integer[],然后后面的处理就没有悬念了。


总结:

文章给出的2+2=5其实并不是真的计算结果是5,而是把计算结果打印成为5,所采用的方案是利用自动装箱时把4装成5。由5在缓存的区间里面,因此修改装箱会引用的那个值,加上自动装箱功能,就可以打印成为5了。由于表演要像一点,因此打印的时候需要选择装箱后打印,这里没有自己写一个Object类型的参数打印方法(Integer类型的,Number类型的都行,只要先装箱就好办了),而是选择了Java中不常用的printf方法。但是这个方法带来的副作用是,4的缓存被指向5了,因此只要是结果为4的自动装箱都将被包装成为5,因此不只是2+2=5了,1+3,6-2都是5了。

不管怎么说,这个还是很有创意的一个想法。而且,由于修改的是Integer类,因此不一定把修改缓存的这部分都写在main方法中,写在静态代码中可能更具有隐蔽性,并且如果静态代码块在引用的别的类中,那么这个隐蔽性就更强了。


© 著作权归作者所有

共有 人打赏支持
于忠达
粉丝 114
博文 13
码字总数 16484
作品 0
青岛
程序员
私信 提问
SQL*Plus中替换变量与定义变量

替换变量 SQL*Plus中的替换变量又叫替代变量,它一般用来临时存储相关数据;在SQL语句之间传递值。一般使用&或&&前缀来指定替换变量. 关于使用替换变量,一般是利用其创建通用的脚本或达到和...

breakawaylove
2014/10/24
26
0
蓝桥杯题目——瓷砖铺放

问题描述   有一长度为N(1<=N<=10)的地板,给定两种不同瓷砖:一种长度为1,另一种长度为2,数目不限。要将这个长度为N的地板铺满,一共有多少种不同的铺法?  例如,长度为4的地面一共...

孤单的狗
2017/03/10
0
0
计算时间复杂度

时间复杂度是总运算次数表达式中受n的变化影响最大的那一项(不含系数) 比如:一般总运算次数表达式类似于这样:a2^n+bn^3+cn^2+dnlg(n)+en+fa ! =0时,时间复杂度就是O(2^n);a=0,b<>0 =>O(n...

JasonWung
2016/07/10
69
0
C++编程实现对车间产品生产周期的估算和生产调度方案的安排算法

花费二个多月的时间编写了可以估算产品生产周期和安排生产调度方案的程序,生产周期的估算,就是在生产过程中有订单插入的情况下计算在工艺文件所规定的工序下,不同种类的多件产品(同一类别...

Kukucao
2018/06/09
0
0
[源码分析]AbstractStringBuilder

[源码分析]AbstractStringBuilder Java中, AbstractStringBuilder是 StringBuilder 和 StringBuffer 的父类. 所以了解StringBuilder和StringBuffer前, 有必要先了解一下这个抽象父类. value...

GoldArowana
2018/08/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Cookie 显示用户上次访问的时间

import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServlet;import javax.serv......

gwl_
今天
1
0
网络编程

第14天 网络编程 今日内容介绍  网络通信协议  UDP通信  TCP通信 今日学习目标  能够辨别UDP和TCP协议特点  能够说出UDP协议下两个常用类名称  能够说出TCP协议下两个常用类名称...

stars永恒
今天
1
0
二进制相关

二进制 众所周知计算机使用的是二进制,数字的二进制是如何表示的呢? 实际就是逢二进一。比如 2 用二进制就是 10。那么根据此可以推算出 5的二进制等于 10*10+1 即为 101。 在计算机中,负数以...

NotFound403
昨天
3
0
day22:

1、写一个getinterface.sh 脚本可以接受选项[i,I],完成下面任务: 1)使用格式:getinterface.sh [-i interface | -I ip] 2)当用户使用-i选项时,显示指定网卡的IP地址;当用户使用-I选项...

芬野de博客
昨天
2
0
Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现

自Spring Cloud Alibaba发布第一个Release以来,就备受国内开发者的高度关注。虽然Spring Cloud Alibaba还没能纳入Spring Cloud的主版本管理中,但是凭借阿里中间件团队的背景,还是得到不少...

程序猿DD
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部