壹、瞒天过海

我打赌你肯定想不到,有人居然会在注释里下了毒。看看下面的代码,简单到main方法中只有一行注释。

public static void main(String[] args) {// \u000d System.out.println("coder Hydra");
}

猜猜看,这段程序运行结果如何?执行后它居然会在控制台打印:

coder Hydra

看到这你是不是一脸懵逼,为什么注释中的代码会被执行?

其实原理就在于大家熟悉的unicode编码,上面的\u000d就是一个unicode转义字符,它所表示的是一个换行符。而java中的编译器,不仅会编译代码,还会解析unicode编码将它替换成对应的字符。所以说,上面的代码解析完后实际是这样的:

public static void main(String[] args) {//System.out.println("coder Hydra");
}

这样,就能解释为什么能够执行注释中的语句了。当然,如果你觉得上面的代码不够绝,想要再绝一点,那么就可以把代码写成下面这个样子。

public static void main(String[] args) {int a=1;// \u000d \u0061\u002b\u002b\u003bSystem.out.println(a);
}

执行结果会打印2,同理,因为后面的unicode编码的转义后表示的是a++;

至于这么写有什么好处,当然是用在某些不想让别人看懂的地方,用来掩人耳目了,估计大家都看过下面这个笑话。

你这么写的话客户如果懂点代码,看一下就穿帮了啊,但是你如果写成下面这样,大部分估计都以为这是一段乱码:

//\u000d\u0054\u0068\u0072\u0065\u0061\u0064\u002e\u0073\u006c\u0065\u0065\u0070\u0028\u0032\u0030\u0030\u0030\u0029\u003b

恕我直言,没个几十年的功力真看不出来这里执行的是sleep,简直完美。

贰、舍近求远

要想写出别人看不懂的代码,很重要的一个小技巧就是把简单的东西复杂化。例如,判断一个int型数字的正负时明明可以写成这样:

public void judge(int x){if (x>0){//...}else if (x<0){//...}
}

但是我偏不,放着简单的代码不用,我就是玩,非要写成下面这样:

public void judge2(int x){if (x>>>31==0){//...}else if (x>>>31==1){//...}
}

怎么样,这么写的话是不是逼格一下子就支棱起来了!别人看到这多少得琢磨一会这块到底写了个啥玩意。

其实原理也很简单,这里用到的>>>是无符号右移操作。举个简单的例子,以-3为例,移位前先转化为它的补码:

11111111111111111111111111111101

无符号右移一位后变成下面的形式,这个数转化为十进制后是2147483646

01111111111111111111111111111110

所以,当一个int类型的数字在无符号右移31位后,其实在前面的31位高位全部是0,剩下的最低位是原来的符号位,因此可以用来判断数字的正负。

基于这个小知识,我们还能整出不少活来。例如,放着好好的0不用,我们可以通过下面的方式定义一个0:

int ZERO=Integer.MAX_VALUE>>31>>1;

通过上面的知识,相信大家可以轻易理解,因为在将一个数字无符号右移32位后,二进制的所有位上全部是0,所以最终会得到0。那么问题来了,我为什么不直接用Integer.MAX_VALUE>>32,一次性右移32位呢?

这是因为在对int型的数字进行移位操作时,会对操作符右边的参数进行模32的取余运算,因此如果直接写32的话,那么相当于什么都不做,得到的还是原数值。

叁、颠倒黑白

古有赵高指鹿为马,今有码农颠倒真假。阻碍同事阅读你代码的有力武器之一,就是让他在遇到条件判断时失去基本判断能力,陷入云里雾里,不知道接下来要走的是哪一个分支。

下面的代码,我说会打印fasle,是不是没有人会信?

public class TrueTest {public static void main(String[] args) {Boolean reality = true;if(reality) {System.out.println("true");} else {System.out.println("false");}}
}

没错,只要大家了解布尔类型就知道这不符合逻辑,但是,经过下面的改造就可以让它变为现实。

首先,在类中找个隐蔽的位置插入下面这段代码:

static {try {Field trueField = Boolean.class.getDeclaredField("TRUE");trueField.setAccessible(true);Field modifiersField = Field.class.getDeclaredField("modifiers");modifiersField.setAccessible(true);modifiersField.setInt(trueField, trueField.getModifiers() & ~Modifier.FINAL);trueField.set(null, false);} catch(IllegalAccessException | NoSuchFieldException e) {e.printStackTrace();}
}

然后再运行上面的程序,你就会发现神奇地打印了false

其实原理也很简单,首先通过反射拿到Boolean类中定义的TRUE这个变量:

public static final Boolean TRUE = new Boolean(true);

接着使用反射,去掉它的final修饰符,最后再将它的值设为false。而在之后再使用true进行定义Boolean类型的变量过程中,会进行自动装箱,调用下面的方法:

public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);
}

这时的btrue,而TRUE实际上是false,因此不满足第一个表达式,最终会返回false

这样一来就能解释上面的打印结果了,不过切记,这么写的时候一定要找一个代码中隐蔽的角落,不要被人发现,否则容易被打的很惨…

肆、化整为零

接下来要介绍的这个技巧就有点厉害了,可以将原有的一段串行逻辑改写成判断逻辑中的不同分支,并且保证最后能够正常执行。

在开始前先提一个问题,有没有一种方法,可以让ifelse中的语句都能执行,就像下面的这个例子中:

public static void judge(String param){if (/*判断条件*/){System.out.println("step one");}else {System.out.println("step two");}
}

如果我说只调用一次这个方法,就能同时输出ifelse中的打印语句,你肯定会说不可能,因为这违背了java中判断逻辑的基本常识。

没错,在限定了上面的修饰语只调用『一次』方法的条件下,谁都无法做到。但是如果在判断条件中动一点点手脚,就能够实现上面提到的功能。看一下改造后的代码:

public class IfTest {public static void main(String[] args) {judge("Hydra");}public static void judge(String param){if (param==null ||new IfTest(){{ IfTest.check(null); }}.equals("Hydra")){System.out.println("step one");}else {System.out.println("step two");}}
}

运行后控制台打印了:

step one
step two

惊不惊喜、意不意外?其实它能够执行的秘密就在if的判断条件中。

当第一次调用judge()方法时,不满足或运算中的第一个条件,因此执行第二个条件,会执行匿名内部类内的实例化初始块代码,再次执行judge()方法,此时满足if条件,因此执行第一句打印语句。

而实例化的新对象不满足后面的equals()方法中的条件,所以不满足if中的任意一个条件,因此会执行else中的语句,执行第二句打印语句。

这样就实现了表面上调用一次方法,同时执行ifelse中的语句块的功能。怎么样,用这种方式把一段整体的逻辑拆成两块,让你的同事迷惑去吧。

伍、釜底抽薪

在程序员的世界里,不同语言之间一直存在鄙视链,例如写c的就看不起写java的,因为直接操作内存啥的看上去就很高大上不是么?那么我们今天就假装自己是一个c语言程序员,来在java中操作一把内存。

具体要怎么做呢,还是要使用java中的魔法类Unsafe。看这个名字也可以明白,这玩意如果使用不当的话不是非常安全,所以获取Unsafe实例也比较麻烦,需要通过反射获取:

Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe =(Unsafe) unsafeField.get(null);

在拿到这个对象后,我们就可以对内存为所欲为了。例如,我们在实现int a=1;这样的简单赋值时,就可以搞复杂点,像下面这样绕一个弯子:

void test(){long addr = unsafe.allocateMemory(4);unsafe.putInt(addr,1);int a=unsafe.getInt(addr);System.out.println(a);unsafe.freeMemory(addr);
}

首先通过allocateMemory方法申请4字节的内存空间后,然后通过putInt方法写入一个1,再从这个地址读取一个int类型长度的变量,最终实现了把1赋值给a的操作。

当然了,还有很多高级一点的用法,这里简单举两个例子。

void test(){long addr = unsafe.allocateMemory(4);unsafe.setMemory(addr,4, (byte) 1);System.out.println(unsafe.getInt(addr));unsafe.freeMemory(addr);
}

上面的代码中,通过setMemory方法向每个字节写入byte类型的1,最后调用getInt方法一次性读取4个字节作为一个int型变量的值。这段代码最终打印结果为16843009,对应的二进制如下:

00000001 00000001 00000001 00000001

至于c语言中的内存复制,用Unsafe搞起来也是信手拈来:

void test2(){long addr = unsafe.allocateMemory(4);long addr2 = unsafe.reallocateMemory(addr, 4 * 2);unsafe.putInt(addr, 1);for (int i = 0; i < 2; i++) {unsafe.copyMemory(addr,addr2+4*i,4);}System.out.println(unsafe.getInt(addr));System.out.println(unsafe.getLong(addr2));unsafe.freeMemory(addr);unsafe.freeMemory(addr2);
}

上面的代码中,通过reallocateMemory方法重新分配了一块8字节长度的内存空间,并把addr开头的4字节内存空间分两次进复制到addr2的内存空间中,上面的代码会打印:

1
4294967297

这是因为新的8字节内存空间addr2中存储的二进制数字是下面这样,转化为十进制的long类型后正好对应4294967297

100000000000000000000000000000001

Unsafe除了能直接操作内存空间外,还有线程调度、对象操作、CAS操作等实用的功n详细的了解一下,

最后

好了,没用的知识介绍环节就此结束,相信大家在掌握了这些技巧后,都能自带代码混淆光环,写出不一样的拉轰代码。

最后建议大家,在项目中这样写代码的时候,搭配红花油、跌打损伤酒一起使用,可能效果更佳。

学习更多JAVA知识与技巧,关注与私信博主(555)!
热爱学习和渴望进阶的小伙伴,各种JAVA学习路线、笔记、面试题,免费分享!

如何写出同事看不懂的Java代码?相关推荐

  1. 七点建议,帮助你编写出简洁、干练的Java代码

    在每一位刚入行的程序员的心中,编写程序都是一门神圣的艺术创作.他们无不希望自己的代码作品既简洁清晰,又可读性强,而且还具有一定的容错能力.本文小千将为您带来七点建议和技巧,以帮助您编写出简洁.干练的J ...

  2. c语言迷宫闯关游戏大全,C语言写出的迷宫闯关游戏代码.doc

    C语言写出的迷宫闯关游戏代码 C语言写出的迷宫闯关游戏代码: #include #include #define LEFT 75 #define RIGHT 77 #define UPPER 72 # ...

  3. Spark 写出MySQL报错,java.sql.BatchUpdateException

    spark DataFrame 写出到MySQL时报如下错误: java.sql.BatchUpdateException: Column 'name' specified twice at sun. ...

  4. 1002 写出这个数 (20分)-Java

    题目 读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值.这里保证 n 小于 10 ​100. 输出格式: 在 ...

  5. 【入门基础】写给小白看的入门级 Java 基本语法

    众所周知,Java 是一门面向对象的编程语言.它最牛逼的地方就在于它是跨平台的,你可以在 Windows 操作系统上编写 Java 源代码,然后在 Linux 操作系统上执行编译后的字节码,而无需对源 ...

  6. python代码怎么写出色_如何写出更具有Python风格的代码,五分钟教会你!

    我们都喜欢 Python,因为它让编程和理解变的更为简单.但是一不小心,我们就会忽略规则,以非 Pythonic 方式编写一堆垃圾代码,从而浪费 Python 这个出色的语言赋予我们的优雅.Pytho ...

  7. 如何才能写出一手高质量优美的代码

    怎么判断代码是否是优质量的代码呢?下面来简单对代码质量的问题进行一个介绍. 代码质量所涉及的5个方面,编码标准.代码重复.代码覆盖率.依赖项分析.复杂度分析.我们分别来看一下这5方面: 编码标准:一般 ...

  8. 【软件开发底层知识修炼】十三 链接器-如何写出不依赖C库函数的代码

    本文将综合以下4篇文章,学习如何写出不依赖libc库的程序: [软件开发底层知识修炼]九 链接器-可重定位文件与可执行文件 [软件开发底层知识修炼]十 链接器-main函数不是第一个被执行的函数 [软 ...

  9. 如何写出更具有Python风格的代码

    我们都喜欢 Python,因为它让编程和理解变的更为简单.但是一不小心,我们就会忽略规则,以非 Pythonic 方式编写一堆垃圾代码,从而浪费 Python 这个出色的语言赋予我们的优雅.Pytho ...

最新文章

  1. 如何正确的对待设计模式——我的观点
  2. 用js操作table、tr、td 「字体样式及TD背景图片」
  3. WPF之坑——ICommandSource与RoutedUICommand
  4. 云上快报 | 阿里云混合云再攀新高
  5. panda python_12个很棒的Pandas和NumPy函数,让分析事半功倍
  6. mysql error report,ECSHOP网店系统提示MYSQL SERVER ERROR REPORT的解决方法
  7. linux退出热键_linux用户退出登录的命令介绍
  8. 今天咱们用 Python 整一个 俄罗斯方块 小游戏吧(附源代码)
  9. 1024,来一套程序员续命操!
  10. 常用统计量及其常见分布
  11. android 圆动画效果,Android实现任意绕圆或椭圆旋转的动画——SatelliteAnimator使用介绍...
  12. CDH5(CDH 5.16.1)安装
  13. MPLS基本部署实验解析(静态LSP与动态LSP)
  14. google的新闻(文章)分类算法
  15. 【Puppeteer】基于Puppeteer采集网页图片资源
  16. Mac 系统下Python多版本管理
  17. networkx2.5知识梳理
  18. appium python自动测试 百度网盘_S35 移动App Appium自动化测试教程Appium+Python 百度云...
  19. 使用python3 爬取豆瓣电影热映和即将上映
  20. Vim q-quit - 用 q 键关闭窗口

热门文章

  1. 一个学员去了互联网大厂一个笔试题分享
  2. 抓紧收藏,教你如何轻松快速度过冷启动时期?短视频新手必看
  3. python应用范围广吗_Python应用范围seo
  4. 长波猝灭剂QSY 21NHS,304014-13-9,QSY21 活性酯特点有哪些
  5. oracle 获取第三行,Oracle 11g 第三章知识点总结——单行函数
  6. DELL笔记本电源检测功率匹配、充不进电
  7. 苏州大学计算机学院江苏如皋人,秦岭深处的火热青春——记苏州大学如皋籍学生佘一奇的支教故事...
  8. 最浅显易懂的Django系列教程(31)-类视图
  9. 一个u盘大小的树莓派就能搭建一个服务器
  10. 新版Qq为什么不受欢迎?