如何写出同事看不懂的Java代码?

如何写出同事看不懂的Java代码?

原创:微信公众号 码农参上,欢迎分享,转载请保留出处。

哈喽大家好啊,我是没更新就是在家忙着带娃的Hydra。

前几天,正巧赶上组里代码review,一下午下来,感觉整个人都血压拉满了。五花八门的代码让我不禁感叹,代码规范这条道路还是任重而道远…

那么今天就来给大家总结一波Java中的代码作死小技巧,熟练掌握这些小技巧后,保证能让你写出同事看不懂的代码~

至于为啥要写出同事看不懂的代码,通过这次教训,我发现好处还是挺多的,简单举几个例子:

  • 同事无法轻易修改你的代码,避免团队协作不当引入bug
  • 塑造个人能力的不可替代性,规避被辞退的风险
  • 代码review时,帮助同事治疗好多年的低血压

好了,一本正经的胡说八道环节就此打住……废话不多说了,下面正式开始。没用的知识又要增加了…

壹、瞒天过海

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

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

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

coder Hydra

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

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

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

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

public static void main(String[] args) { int a=1; // a++; System.out.println(a);}

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

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

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

//Thread.sleep(2000);

恕我直言,没个几十年的功力真看不出来这里执行的是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);}

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

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

肆、化整为零

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

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

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

如果我说只调用一次这个方法,就能同时输出if和else中的打印语句,你肯定会说不可能,因为这违背了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 onestep two

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

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

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

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

伍、釜底抽薪

在程序员的世界里,不同语言之间一直存在鄙视链,例如写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的内存空间中,上面的代码会打印:

14294967297

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

100000000000000000000000000000001

Unsafe除了能直接操作内存空间外,还有线程调度、对象操作、CAS操作等实用的功能,如果想详细的了解一下,可以看看这篇Java双刃剑之Unsafe类详解,开启新世界的大门。

最后

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

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

那么,这次的分享就到这里,我是Hydra,下篇文章再见。

作者简介,码农参上,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。欢迎添加好友,进一步交流。

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月22日
下一篇 2022年6月22日

相关推荐

  • 蔚来回应被指“冷血”,蔚来公关:水军搞不倒我

    蔚来测试车意外坠楼,导致两名试车员身亡,而蔚来的回应直接将此事推向高潮。 6月23日,#蔚来汽车被曝冲出总部大楼坠楼#、#蔚来汽车坠楼两名试车员身亡#陆续冲上微博热搜。综合报道,位…

    2022年6月28日
  • 女人在30岁前,应尽量避开几种“显老”的发型,这样弄简单又好看

    今天我们来讨论一个“非专业的典型问题”:一个人的视觉年龄主要从哪里体现?就是,你觉得别人第一眼判断你年纪的时候,是依据什么? 我想,大部分人应该都是根据以下两点来做第一眼判断的吧。…

    2022年5月16日
  • SPL 提速天体聚类任务 2000 倍

    问题描述 国家天文台有个聚类任务:共 11 份数据,每份数据是从一张照片中提取出来的,包含 500 多万条记录,每条记录是一个天体的坐标及属性。11 张“照片”中有些天体坐标是重复…

    2022年8月17日
  • 治疗白癜风的常见方法有?

      白癜风对患者的身体伤害比较大,会严重破坏患者体内的黑色素细胞,引起皮肤白斑,这些白斑好发于暴露的部位,比如脸部、头部、脖子等,对患者的个人形象也会造成比较大的影响,但是很多白癜…

    2022年8月4日
  • 《战神5》黑谷盔甲怎么获得?黑暗谷盔甲获得方法介绍

    战神5是一款剧情类的角色扮演动作游戏,现已正式上线了,很多小伙伴可能还不清楚游戏中的黑谷盔甲怎么获得吧,今天小编给大家带来战神5黑暗谷盔甲获得方法介绍,快来看一下吧。 黑暗谷盔甲获…

    2022年11月9日
  • Deno 核心开发者开源 Fresh,宣称是下一代 Web 框架

    出品|开源中国 Deno 核心开发者 Luca Casonato 开源了名为「Fresh」的新项目,并称它是下一代的 Web 框架。 根据官网的介绍,Fresh 是面向 JavaS…

    2022年6月18日
  • 红枣菊花茶怎么泡好喝

    红枣菊花茶是生活中最常见的一种养生茶,它能为身体补充,丰富营养并能清肝明目,而且能补益气血,不过红枣菊花茶也有多种不同的冲泡方法,今天我会做这方面的介绍,能让大家了解红枣菊花茶怎么…

    2022年5月15日
  • 使用自己的 Apple ID 有哪些方便?

    和单纯用 iPhone 、 iPad 等产品比起来,Apple ID 在很多叔叔阿姨心目中可能还略显神秘。 因为记不住密码、嫌麻烦或者不会设置等原因,有些叔叔阿姨并没有在设备上注册…

    2022年7月30日
  • 英雄联盟手游与端游的区别

    端游老玩家,lol手游目前15级。 说一下优缺点还有建议。 我个人觉得对比端游,还原度比较高了。 从装备到英雄到技能,还原度有70%以上。 不少英雄和装备做了改动。 由于我玩的英雄…

    2022年6月29日
  • 4399团队出的「古荒遗迹」手游-首日体验评测

    小杨今天新开的栏目《新游推荐》,主要是发布当天市面首发的游戏攻略,均为个人体验后的原创评测,没有被充值!没有被充值!没有被充值!我只会发有意思的游戏,喜欢的小伙伴可以一键三连,谢谢…

    2022年8月6日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息