在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    如何寫出同事看不懂的Java代碼?

    如何寫出同事看不懂的Java代碼?

    原創(chuàng):微信公眾號 碼農(nóng)參上,歡迎分享,轉(zhuǎn)載請保留出處。

    哈嘍大家好啊,我是沒更新就是在家忙著帶娃的Hydra。

    前幾天,正巧趕上組里代碼review,一下午下來,感覺整個人都血壓拉滿了。五花八門的代碼讓我不禁感嘆,代碼規(guī)范這條道路還是任重而道遠…

    那么今天就來給大家總結(jié)一波Java中的代碼作死小技巧,熟練掌握這些小技巧后,保證能讓你寫出同事看不懂的代碼~

    至于為啥要寫出同事看不懂的代碼,通過這次教訓(xùn),我發(fā)現(xiàn)好處還是挺多的,簡單舉幾個例子:

    • 同事無法輕易修改你的代碼,避免團隊協(xié)作不當(dāng)引入bug
    • 塑造個人能力的不可替代性,規(guī)避被辭退的風(fēng)險
    • 代碼review時,幫助同事治療好多年的低血壓

    好了,一本正經(jīng)的胡說八道環(huán)節(jié)就此打住……廢話不多說了,下面正式開始。沒用的知識又要增加了…

    壹、瞞天過海

    我打賭你肯定想不到,有人居然會在注釋里下了毒??纯聪旅娴拇a,簡單到main方法中只有一行注釋。

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

    猜猜看,這段程序運行結(jié)果如何?執(zhí)行后它居然會在控制臺打印:

    coder Hydra

    看到這你是不是一臉懵逼,為什么注釋中的代碼會被執(zhí)行?

    其實原理就在于大家熟悉的unicode編碼,上面的就是一個unicode轉(zhuǎn)義字符,它所表示的是一個換行符。而java中的編譯器,不僅會編譯代碼,還會解析unicode編碼將它替換成對應(yīng)的字符。所以說,上面的代碼解析完后實際是這樣的:

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

    這樣,就能解釋為什么能夠執(zhí)行注釋中的語句了。當(dāng)然,如果你覺得上面的代碼不夠絕,想要再絕一點,那么就可以把代碼寫成下面這個樣子。

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

    執(zhí)行結(jié)果會打印2,同理,因為后面的unicode編碼的轉(zhuǎn)義后表示的是a++;。

    至于這么寫有什么好處,當(dāng)然是用在某些不想讓別人看懂的地方,用來掩人耳目了,估計大家都看過下面這個笑話。

    你這么寫的話客戶如果懂點代碼,看一下就穿幫了啊,但是你如果寫成下面這樣,大部分估計都以為這是一段亂碼:

    //Thread.sleep(2000);

    恕我直言,沒個幾十年的功力真看不出來這里執(zhí)行的是sleep,簡直完美。

    貳、舍近求遠

    要想寫出別人看不懂的代碼,很重要的一個小技巧就是把簡單的東西復(fù)雜化。例如,判斷一個int型數(shù)字的正負時明明可以寫成這樣:

    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為例,移位前先轉(zhuǎn)化為它的補碼:

    11111111111111111111111111111101

    無符號右移一位后變成下面的形式,這個數(shù)轉(zhuǎn)化為十進制后是2147483646。

    01111111111111111111111111111110

    所以,當(dāng)一個int類型的數(shù)字在無符號右移31位后,其實在前面的31位高位全部是0,剩下的最低位是原來的符號位,因此可以用來判斷數(shù)字的正負。

    基于這個小知識,我們還能整出不少活來。例如,放著好好的0不用,我們可以通過下面的方式定義一個0:

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

    通過上面的知識,相信大家可以輕易理解,因為在將一個數(shù)字無符號右移32位后,二進制的所有位上全部是0,所以最終會得到0。那么問題來了,我為什么不直接用Integer.MAX_VALUE>>32,一次性右移32位呢?

    這是因為在對int型的數(shù)字進行移位操作時,會對操作符右邊的參數(shù)進行模32的取余運算,因此如果直接寫32的話,那么相當(dāng)于什么都不做,得到的還是原數(shù)值。

    叁、顛倒黑白

    古有趙高指鹿為馬,今有碼農(nóng)顛倒真假。阻礙同事閱讀你代碼的有力武器之一,就是讓他在遇到條件判斷時失去基本判斷能力,陷入云里霧里,不知道接下來要走的是哪一個分支。

    下面的代碼,我說會打印fasle,是不是沒有人會信?

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

    沒錯,只要大家了解布爾類型就知道這不符合邏輯,但是,經(jīng)過下面的改造就可以讓它變?yōu)楝F(xiàn)實。

    首先,在類中找個隱蔽的位置插入下面這段代碼:

    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(); }}

    然后再運行上面的程序,你就會發(fā)現(xiàn)神奇地打印了false。

    其實原理也很簡單,首先通過反射拿到Boolean類中定義的TRUE這個變量:

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

    接著使用反射,去掉它的final修飾符,最后再將它的值設(shè)為false。而在之后再使用true進行定義Boolean類型的變量過程中,會進行自動裝箱,調(diào)用下面的方法:

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

    這時的b為true,而TRUE實際上是false,因此不滿足第一個表達式,最終會返回false。

    這樣一來就能解釋上面的打印結(jié)果了,不過切記,這么寫的時候一定要找一個代碼中隱蔽的角落,不要被人發(fā)現(xiàn),否則容易被打的很慘…

    肆、化整為零

    接下來要介紹的這個技巧就有點厲害了,可以將原有的一段串行邏輯改寫成判斷邏輯中的不同分支,并且保證最后能夠正常執(zhí)行。

    在開始前先提一個問題,有沒有一種方法,可以讓if和else中的語句都能執(zhí)行,就像下面的這個例子中:

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

    如果我說只調(diào)用一次這個方法,就能同時輸出if和else中的打印語句,你肯定會說不可能,因為這違背了java中判斷邏輯的基本常識。

    沒錯,在限定了上面的修飾語只調(diào)用『一次』方法的條件下,誰都無法做到。但是如果在判斷條件中動一點點手腳,就能夠?qū)崿F(xiàn)上面提到的功能??匆幌赂脑旌蟮拇a:

    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

    驚不驚喜、意不意外?其實它能夠執(zhí)行的秘密就在if的判斷條件中。

    當(dāng)?shù)谝淮握{(diào)用judge()方法時,不滿不或運算中的第一個條件,因此執(zhí)行第二個條件,會執(zhí)行匿名內(nèi)部類內(nèi)的實例化初始塊代碼,再次執(zhí)行judge()方法,此時滿足if條件,因此執(zhí)行第一句打印語句。

    而實例化的新對象不滿足后面的equals()方法中的條件,所以不滿足if中的任意一個條件,因此會執(zhí)行else中的語句,執(zhí)行第二句打印語句。

    這樣就實現(xiàn)了表面上調(diào)用一次方法,同時執(zhí)行if和else中的語句塊的功能。怎么樣,用這種方式把一段整體的邏輯拆成兩塊,讓你的同事迷惑去吧。

    伍、釜底抽薪

    在程序員的世界里,不同語言之間一直存在鄙視鏈,例如寫c的就看不起寫java的,因為直接操作內(nèi)存啥的看上去就很高大上不是么?那么我們今天就假裝自己是一個c語言程序員,來在java中操作一把內(nèi)存。

    具體要怎么做呢,還是要使用java中的魔法類Unsafe??催@個名字也可以明白,這玩意如果使用不當(dāng)?shù)脑挷皇欠浅0踩?,所以獲取Unsafe實例也比較麻煩,需要通過反射獲?。?/p>

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

    在拿到這個對象后,我們就可以對內(nèi)存為所欲為了。例如,我們在實現(xiàn)int a=1;這樣的簡單賦值時,就可以搞復(fù)雜點,像下面這樣繞一個彎子:

    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字節(jié)內(nèi)存空間后,然后通過putInt方法寫入一個1,再從這個地址讀取一個int類型長度的變量,最終實現(xiàn)了把1賦值給a的操作。

    當(dāng)然了,還有很多高級一點的用法,這里簡單舉兩個例子。

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

    上面的代碼中,通過setMemory方法向每個字節(jié)寫入byte類型的1,最后調(diào)用getInt方法一次性讀取4個字節(jié)作為一個int型變量的值。這段代碼最終打印結(jié)果為16843009,對應(yīng)的二進制如下:

    00000001 00000001 00000001 00000001

    至于c語言中的內(nèi)存復(fù)制,用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字節(jié)長度的內(nèi)存空間,并把addr開頭的4字節(jié)內(nèi)存空間分兩次進復(fù)制到addr2的內(nèi)存空間中,上面的代碼會打?。?/p>

    14294967297

    這是因為新的8字節(jié)內(nèi)存空間addr2中存儲的二進制數(shù)字是下面這樣,轉(zhuǎn)化為十進制的long類型后正好對應(yīng)4294967297。

    100000000000000000000000000000001

    Unsafe除了能直接操作內(nèi)存空間外,還有線程調(diào)度、對象操作、CAS操作等實用的功能,如果想詳細的了解一下,可以看看這篇Java雙刃劍之Unsafe類詳解,開啟新世界的大門。

    最后

    好了,沒用的知識介紹環(huán)節(jié)就此結(jié)束,相信大家在掌握了這些技巧后,都能自帶代碼混淆光環(huán),寫出不一樣的拉轟代碼。

    最后建議大家,在項目中這樣寫代碼的時候,搭配紅花油、跌打損傷酒一起使用,可能效果更佳。

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

    作者簡介,碼農(nóng)參上,一個熱愛分享的公眾號,有趣、深入、直接,與你聊聊技術(shù)。歡迎添加好友,進一步交流。

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
    用戶投稿
    上一篇 2022年6月22日 09:19
    下一篇 2022年6月22日 09:19

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時間:周一至周五,10:30-18:30,節(jié)假日休息