不瞭解這12個語法糖,別說你會Java!
本文從 Java 編譯原理角度,深入位元組碼及 class 檔案,抽絲剝繭,瞭解 Java 中的語法糖原理及用法,幫助大家在學會如何使用 Java 語法糖的同時,瞭解這些語法糖背後的原理
語法糖
語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家 Peter.J.Landin 發明的一個術語,指在計算機語言中新增的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。簡而言之,語法糖讓程式更加簡潔,有更高的可讀性。
有意思的是,在程式設計領域,除了語法糖,還有語法鹽和語法糖精的說法,篇幅有限這裡不做擴充套件了。
我們所熟知的程式語言中幾乎都有語法糖。作者認為,語法糖的多少是評判一個語言夠不夠牛逼的標準之一。
很多人說Java是一個“低糖語言”,其實從Java 7開始Java語言層面上一直在新增各種糖,主要是在“Project Coin”專案下研發。儘管現在Java有人還是認為現在的Java是低糖,未來還會持續向著“高糖”的方向發展。
解語法糖
前面提到過,語法糖的存在主要是方便開發人員使用。但其實,Java虛擬機器並不支援這些語法糖。這些語法糖在編譯階段就會被還原成簡單的基礎語法結構,這個過程就是解語法糖。
說到編譯,大家肯定都知道,Java語言中,javac命令可以將字尾名為.java的原始檔編譯為字尾名為.class的可以運行於Java虛擬機器的位元組碼。
如果你去看com.sun.tools.javac.main.JavaCompiler的原始碼,你會發現在compile()中有一個步驟就是呼叫desugar(),這個方法就是負責解語法糖的實現的。
Java 中最常用的語法糖主要有泛型、變長引數、條件編譯、自動拆裝箱、內部類等。本文主要來分析下這些語法糖背後的原理。一步一步剝去糖衣,看看其本質。
糖塊一、 switch 支援 String 與列舉
前面提到過,從Java 7 開始,Java語言中的語法糖在逐漸豐富,其中一個比較重要的就是Java 7中switch開始支援String。
在開始coding之前先科普下,Java中的swith自身原本就支援基本型別。比如int、char等。
對於int型別,直接進行數值的比較。對於char型別則是比較其ascii碼。
所以,對於編譯器來說,switch中其實只能使用整型,任何型別的比較都要轉換成整型。比如byte。short,char(ackii碼是整型)以及int。
那麼接下來看下switch對String得支援,有以下程式碼:
public class switchDemoString { public static void main(String[] args) { String str = "world"; switch (str) { case "hello": System.out.println("hello"); break; case "world": System.out.println("world"); break; default: break; } } }
反編譯後內容如下:
public class switchDemoString { public switchDemoString() { } public static void main(String args[]) { String str = "world"; String s; switch((s = str).hashCode()) { default: break; case 99162322: if(s.equals("hello")) System.out.println("hello"); break; case 113318802: if(s.equals("world")) System.out.println("world"); break; } } }
看到這個程式碼,你知道原來字串的switch是通過equals()和hashCode()方法來實現的。還好hashCode()方法返回的是int,而不是long。
仔細看下可以發現,進行switch的實際是雜湊值,然後通過使用equals方法比較進行安全檢查,這個檢查是必要的,因為雜湊可能會發生碰撞。因此它的效能是不如使用列舉進行switch或者使用純整數常量,但這也不是很差。
糖塊二、 泛型
我們都知道,很多語言都是支援泛型的,但是很多人不知道的是,不同的編譯器對於泛型的處理方式是不同的。
通常情況下,一個編譯器處理泛型有兩種方式:Code specialization和Code sharing。
C++和C#是使用Code specialization的處理機制,而Java使用的是Code sharing的機制。
Code sharing方式為每個泛型型別建立唯一的位元組碼錶示,並且將該泛型型別的例項都對映到這個唯一的位元組碼錶示上。將多種泛型類形例項對映到唯一的位元組碼錶示是通過型別擦除(type erasue)實現的。
也就是說,對於Java虛擬機器來說,他根本不認識Map<String, String> map這樣的語法。需要在編譯階段通過型別擦除的方式進行解語法糖。
型別擦除的主要過程如下:
-
1.將所有的泛型引數用其最左邊界(最頂級的父型別)型別替換。
-
2.移除所有的型別引數。
以下程式碼:
Map<String, String> map = new HashMap<String, String>(); map.put("name", "hollis"); map.put("wechat", "Hollis"); map.put("blog", "www.hollischuang.com");
解語法糖之後會變成:
Map map = new HashMap(); map.put("name", "hollis"); map.put("wechat", "Hollis"); map.put("blog", "www.hollischuang.com");
以下程式碼:
public static <A extends Comparable<A>> A max(Collection<A> xs) { Iterator<A> xi = xs.iterator(); A w = xi.next(); while (xi.hasNext()) { A x = xi.next(); if (w.compareTo(x) < 0) w = x; } return w; }
型別擦除後會變成:
public static Comparable max(Collection xs){ Iterator xi = xs.iterator(); Comparable w = (Comparable)xi.next(); while(xi.hasNext()) { Comparable x = (Comparable)xi.next(); if(w.compareTo(x) < 0) w = x; } return w; }
虛擬機器中沒有泛型,只有普通類和普通方法,所有泛型類的型別引數在編譯時都會被擦除,泛型類並沒有自己獨有的Class類物件。比如並不存在List<String>.class或是List<Integer>.class,而只有List.class。
糖塊三、 自動裝箱與拆箱
自動裝箱就是Java自動將原始型別值轉換成對應的物件,比如將int的變數轉換成Integer物件,這個過程叫做裝箱,反之將Integer物件轉換成int型別值,這個過程叫做拆箱。參考:一文讀懂什麼是Java中的自動拆裝箱
因為這裡的裝箱和拆箱是自動進行的非人為轉換,所以就稱作為自動裝箱和拆箱。
原始型別byte, short, char, int, long, float, double 和 boolean 對應的封裝類為Byte, Short, Character, Integer, Long, Float, Double, Boolean。
先來看個自動裝箱的程式碼:
public static void main(String[] args) { int i = 10; Integer n = i; }
反編譯後代碼如下:
public static void main(String args[]) { int i = 10; Integer n = Integer.valueOf(i); }
再來看個自動拆箱的程式碼:
public static void main(String[] args) { Integer i = 10; int n = i; }
反編譯後代碼如下:
public static void main(String args[]) { Integer i = Integer.valueOf(10); int n = i.intValue(); }
從反編譯得到內容可以看出,在裝箱的時候自動呼叫的是Integer的valueOf(int)方法。而在拆箱的時候自動呼叫的是Integer的intValue方法。
所以,裝箱過程是通過呼叫包裝器的valueOf方法實現的,而拆箱過程是通過呼叫包裝器的 xxxValue方法實現的。
糖塊四 、 方法變長引數
可變引數(variable arguments)是在Java 1.5中引入的一個特性。它允許一個方法把任意數量的值作為引數。
看下以下可變引數程式碼,其中print方法接收可變引數:
public static void main(String[] args) { print("Holis", "公眾號:Hollis", "部落格:www.hollischuang.com", "QQ:907607222"); } public static void print(String... strs) { for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); } }
反編譯後代碼:
public static void main(String args[]) { print(new String[] { "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222" }); } public static transient void print(String strs[]) { for(int i = 0; i < strs.length; i++) System.out.println(strs[i]); }
從反編譯後代碼可以看出,可變引數在被使用的時候,他首先會建立一個數組,陣列的長度就是呼叫該方法是傳遞的實參的個數,然後再把引數值全部放到這個陣列當中,然後再把這個陣列作為引數傳遞到被呼叫的方法中。
糖塊五 、 列舉
Java SE5提供了一種新的型別-Java的列舉型別,關鍵字enum可以將一組具名的值的有限集合建立為一種新的型別,而這些具名的值可以作為常規的程式元件使用,這是一種非常有用的功能。參考:Java的列舉型別用法介紹
要想看原始碼,首先得有一個類吧,那麼列舉型別到底是什麼類呢?是enum嗎?
答案很明顯不是,enum就和class一樣,只是一個關鍵字,他並不是一個類。
那麼列舉是由什麼類維護的呢,我們簡單的寫一個列舉:
public enum t { SPRING,SUMMER; }
然後我們使用反編譯,看看這段程式碼到底是怎麼實現的,反編譯後代碼內容如下:
public final class T extends Enum { private T(String s, int i) { super(s, i); } public static T[] values() { T at[]; int i; T at1[]; System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i); return at1; } public static T valueOf(String s) { return (T)Enum.valueOf(demo/T, s); } public static final T SPRING; public static final T SUMMER; private static final T ENUM$VALUES[]; static { SPRING = new T("SPRING", 0); SUMMER = new T("SUMMER", 1); ENUM$VALUES = (new T[] { SPRING, SUMMER }); } }
通過反編譯後代碼我們可以看到,public final class T extends Enum,說明,該類是繼承了Enum類的,同時final關鍵字告訴我們,這個類也是不能被繼承的。
當我們使用enmu來定義一個列舉型別的時候,編譯器會自動幫我們建立一個final型別的類繼承Enum類,所以列舉型別不能被繼承。
糖塊六 、 內部類
內部類又稱為巢狀類,可以把內部類理解為外部類的一個普通成員。
內部類之所以也是語法糖,是因為它僅僅是一個編譯時的概念。
outer.java裡面定義了一個內部類inner,一旦編譯成功,就會生成兩個完全不同的.class檔案了,分別是outer.class和outer$inner.class。所以內部類的名字完全可以和它的外部類名字相同。
public class OutterClass { private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public static void main(String[] args) { } class InnerClass{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } }
以上程式碼編譯後會生成兩個class檔案:OutterClass$InnerClass.class 、OutterClass.class 。
當我們嘗試使用jad對OutterClass.class檔案進行反編譯的時候,命令列會列印以下內容:
Parsing OutterClass.class... Parsing inner class OutterClass$InnerClass.class... Generating OutterClass.jad
他會把兩個檔案全部進行反編譯,然後一起生成一個OutterClass.jad檔案。檔案內容如下:
public class OutterClass { class InnerClass { public String getName() { return name; } public void setName(String name) { this.name = name; } private String name; final OutterClass this$0; InnerClass() { this.this$0 = OutterClass.this; super(); } } public OutterClass() { } public String getUserName() { return userName; } public void setUserName(String userName){ this.userName = userName; } public static void main(String args1[]) { } private String userName; }
糖塊七 、條件編譯
—般情況下,程式中的每一行程式碼都要參加編譯。但有時候出於對程式程式碼優化的考慮,希望只對其中一部分內容進行編譯,此時就需要在程式中加上條件,讓編譯器只對滿足條件的程式碼進行編譯,將不滿足條件的程式碼捨棄,這就是條件編譯。
如在C或CPP中,可以通過預處理語句來實現條件編譯。其實在Java中也可實現條件編譯。我們先來看一段程式碼:
public class ConditionalCompilation { public static void main(String[] args) { final boolean DEBUG = true; if(DEBUG) { System.out.println("Hello, DEBUG!"); } final boolean ONLINE = false; if(ONLINE){ System.out.println("Hello, ONLINE!"); } } }
反編譯後代碼如下:
public class ConditionalCompilation { public ConditionalCompilation() { } public static void main(String args[]) { boolean DEBUG = true; System.out.println("Hello, DEBUG!"); boolean ONLINE = false; } }
首先,我們發現,在反編譯後的程式碼中沒有System.out.println("Hello, ONLINE!");,這其實就是條件編譯。
當if(ONLINE)為false的時候,編譯器就沒有對其內的程式碼進行編譯。
所以,Java語法的條件編譯,是通過判斷條件為常量的if語句實現的。根據if判斷條件的真假,編譯器直接把分支為false的程式碼塊消除。通過該方式實現的條件編譯,必須在方法體內實現,而無法在正整個Java類的結構或者類的屬性上進行條件編譯。
這與C/C++的條件編譯相比,確實更有侷限性。在Java語言設計之初並沒有引入條件編譯的功能,雖有侷限,但是總比沒有更強。
糖塊八 、 斷言
在Java中,assert關鍵字是從JAVA SE 1.4 引入的,為了避免和老版本的Java程式碼中使用了assert關鍵字導致錯誤,Java在執行的時候預設是不啟動斷言檢查的(這個時候,所有的斷言語句都將忽略!)。
如果要開啟斷言檢查,則需要用開關-enableassertions或-ea來開啟。
看一段包含斷言的程式碼:
public class AssertTest { public static void main(String args[]) { int a = 1; int b = 1; assert a == b; System.out.println("公眾號:Hollis"); assert a != b : "Hollis"; System.out.println("部落格:www.hollischuang.com"); } }
反編譯後代碼如下:
public class AssertTest { public AssertTest() { } public static void main(String args[]) { int a = 1; int b = 1; if(!$assertionsDisabled && a != b) throw new AssertionError(); System.out.println("\u516C\u4F17\u53F7\uFF1AHollis"); if(!$assertionsDisabled && a == b) { throw new AssertionError("Hollis"); } else { System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com"); return; } } static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus(); }
很明顯,反編譯之後的程式碼要比我們自己的程式碼複雜的多。所以,使用了assert這個語法糖我們節省了很多程式碼。
其實斷言的底層實現就是if語言,如果斷言結果為true,則什麼都不做,程式繼續執行,如果斷言結果為false,則程式丟擲AssertError來打斷程式的執行。
-enableassertions會設定$assertionsDisabled欄位的值。
糖塊九 、 數值字面量
在java 7中,數值字面量,不管是整數還是浮點數,都允許在數字之間插入任意多個下劃線。這些下劃線不會對字面量的數值產生影響,目的就是方便閱讀。
比如:
public class Test { public static void main(String... args) { int i = 10_000; System.out.println(i); } }
反編譯後:
public class Test { public static void main(String[] args) { int i = 10000; System.out.println(i); } }
反編譯後就是把_刪除了。也就是說編譯器並不認識在數字字面量中的_,需要在編譯階段把他去掉。
糖塊十 、 for-each
增強for迴圈(for-each)相信大家都不陌生,日常開發經常會用到的,他會比for迴圈要少寫很多程式碼,那麼這個語法糖背後是如何實現的呢?
public static void main(String... args) { String[] strs = {"Hollis", "公眾號:Hollis", "部落格:www.hollischuang.com"}; for (String s : strs) { System.out.println(s); } List<String> strList = ImmutableList.of("Hollis", "公眾號:Hollis", "部落格:www.hollischuang.com"); for (String s : strList) { System.out.println(s); } }
反編譯後代碼如下:
public static transient void main(String args[]) { String strs[] = { "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com" }; String args1[] = strs; int i = args1.length; for(int j = 0; j < i; j++) { String s = args1[j]; System.out.println(s); } List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"); String s; for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s)) s = (String)iterator.next(); }
程式碼很簡單,for-each的實現原理其實就是使用了普通的for迴圈和迭代器。
糖塊十一 、 try-with-resource
Java裡,對於檔案操作IO流、資料庫連線等開銷非常昂貴的資源,用完之後必須及時通過close方法將其關閉,否則資源會一直處於開啟狀態,可能會導致記憶體洩露等問題。
關閉資源的常用方式就是在finally塊裡是釋放,即呼叫close方法。比如,我們經常會寫這樣的程式碼:
public static void main(String[] args) { BufferedReader br = null; try { String line; br = new BufferedReader(new FileReader("d:\\hollischuang.xml")); while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // handle exception } finally { try { if (br != null) { br.close(); } } catch (IOException ex) { // handle exception } } }
從Java 7開始,jdk提供了一種更好的方式關閉資源,使用try-with-resources語句,改寫一下上面的程式碼,效果如下:
public static void main(String... args) { try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // handle exception } }
看,這簡直是一大福音啊,雖然我之前一般使用IOUtils去關閉流,並不會使用在finally中寫很多程式碼的方式,但是這種新的語法糖看上去好像優雅很多呢。
反編譯以上程式碼,看下他的背後原理:
public static transient void main(String args[]) { BufferedReader br; Throwable throwable; br = new BufferedReader(new FileReader("d:\\ hollischuang.xml")); throwable = null; String line; try { while((line = br.readLine()) != null) System.out.println(line); } catch(Throwable throwable2) { throwable = throwable2; throw throwable2; } if(br != null) if(throwable != null) try { br.close(); } catch(Throwable throwable1) { throwable.addSuppressed(throwable1); } else br.close(); break MISSING_BLOCK_LABEL_113; Exception exception; exception; if(br != null) if(throwable != null) try { br.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } else br.close(); throw exception; IOException ioexception; ioexception; } }
其實背後的原理也很簡單,那些我們沒有做的關閉資源的操作,編譯器都幫我們做了。
所以,再次印證了,語法糖的作用就是方便程式設計師的使用,但最終還是要轉成編譯器認識的語言。
糖塊十二、Lambda表示式
關於lambda表示式,有人可能會有質疑,因為網上有人說他並不是語法糖。其實我想糾正下這個說法。
Labmda表示式不是匿名內部類的語法糖,但是他也是一個語法糖。實現方式其實是依賴了幾個JVM底層提供的lambda相關api。
先來看一個簡單的lambda表示式。遍歷一個list:
public static void main(String... args) { List<String> strList = ImmutableList.of("Hollis", "公眾號:Hollis", "部落格:www.hollischuang.com"); strList.forEach( s -> { System.out.println(s); } ); }
為啥說他並不是內部類的語法糖呢,前面講內部類我們說過,內部類在編譯之後會有兩個class檔案,但是,包含lambda表示式的類編譯後只有一個檔案。
反編譯後代碼如下:
public static /* varargs */ void main(String ... args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com"); strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)()); } private static /* synthetic */ void lambda$main$0(String s) { System.out.println(s); }
可以看到,在forEach方法中,其實是呼叫了java.lang.invoke.LambdaMetafactory#metafactory方法,該方法的第四個引數implMethod指定了方法實現。可以看到這裡其實是呼叫了一個lambda$main$0方法進行了輸出。
再來看一個稍微複雜一點的,先對List進行過濾,然後再輸出:
public static void main(String... args) { List<String> strList = ImmutableList.of("Hollis", "公眾號:Hollis", "部落格:www.hollischuang.com"); List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList()); HollisList.forEach( s -> { System.out.println(s); } ); }
反編譯後代碼如下:
public static /* varargs */ void main(String ... args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com"); List<Object> HollisList = strList.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList()); HollisList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)()); } private static /* synthetic */ void lambda$main$1(Object s) { System.out.println(s); } private static /* synthetic */ boolean lambda$main$0(String string) { return string.contains("Hollis"); }
兩個lambda表示式分別呼叫了lambda$main$1和lambda$main$0兩個方法。
所以,lambda表示式的實現其實是依賴了一些底層的api,在編譯階段,編譯器會把lambda表示式進行解糖,轉換成呼叫內部api的方式。
可能遇到的坑
泛型——當泛型遇到過載
public class GenericTypes { public static void method(List<String> list) { System.out.println("invoke method(List<String> list)"); } public static void method(List<Integer> list) { System.out.println("invoke method(List<Integer> list)"); } }
上面這段程式碼,有兩個過載的函式,因為他們的引數型別不同,一個是List另一個是List,但是,這段程式碼是編譯通不過的。因為我們前面講過,引數List和List編譯之後都被擦除了,變成了一樣的原生型別List,擦除動作導致這兩個方法的特徵簽名變得一模一樣。
泛型——當泛型遇到catch
泛型的型別引數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在執行時刻來進行的。由於型別資訊被擦除,JVM是無法區分兩個異常型別MyException<String>和MyException<Integer>的
泛型——當泛型內包含靜態變數
public class StaticTest{ public static void main(String[] args){ GT<Integer> gti = new GT<Integer>(); gti.var=1; GT<String> gts = new GT<String>(); gts.var=2; System.out.println(gti.var); } } class GT<T>{ public static int var=0; public void nothing(T x){} }
以上程式碼輸出結果為:2!由於經過型別擦除,所有的泛型類例項都關聯到同一份位元組碼上,泛型類的所有靜態變數是共享的。
自動裝箱與拆箱——物件相等比較
public static void main(String[] args) { Integer a = 1000; Integer b = 1000; Integer c = 100; Integer d = 100; System.out.println("a == b is " + (a == b)); System.out.println(("c == d is " + (c == d))); }
輸出結果:
a == b is false c == d is true
在Java 5中,在Integer的操作上引入了一個新功能來節省記憶體和提高效能。整型物件通過使用相同的物件引用實現了快取和重用。
適用於整數值區間-128 至 +127。
只適用於自動裝箱。使用建構函式建立物件不適用。
增強for迴圈
for (Student stu : students) { if (stu.getId() == 2) students.remove(stu); }
會丟擲ConcurrentModificationException異常。
Iterator是工作在一個獨立的執行緒中,並且擁有一個 mutex 鎖。 Iterator被建立之後會建立一個指向原來物件的單鏈索引表,當原來的物件數量發生變化時,這個索引表的內容不會同步改變,所以當索引指標往後移動的時候就找不到要迭代的物件,所以按照 fail-fast 原則 Iterator 會馬上丟擲java.util.ConcurrentModificationException異常。參考:一不小心就讓Java開發者踩坑的fail-fast是個什麼鬼?
所以 Iterator 在工作的時候是不允許被迭代的物件被改變的。但你可以使用 Iterator 本身的方法remove()來刪除物件,Iterator.remove() 方法會在刪除當前迭代物件的同時維護索引的一致性。
總結
前面介紹了12種Java中常用的語法糖。所謂語法糖就是提供給開發人員便於開發的一種語法而已。
但是這種語法只有開發人員認識。要想被執行,需要進行解糖,即轉成JVM認識的語法。
當我們把語法糖解糖之後,你就會發現其實我們日常使用的這些方便的語法,其實都是一些其他更簡單的語法構成的。
有了這些語法糖,我們在日常開發的時候可以大大提升效率,但是同時也要避免過渡使用。使用之前最好了解下原理,避免掉坑。
相關推薦
不瞭解這12個語法糖,別說你會Java!
本文從 Java 編譯原理角度,深入位元組碼及 class 檔案,抽絲剝繭,瞭解 Java 中的語法糖原理及用法,幫助大家在學會如
用好這6個新功能,讓你的項目設計事半功倍
本文由Mockplus提供素材。 Mockplus3.2新功能發布有段時間了,新功能具體能給我們的設計帶來什麽呢?且聽我一一道來: 格子 格子功能,可以快速制作重復的布局,提高設計效率。 步驟: 一
菜鳥們,通過這幾個樣例,包你學會 uptime 命令的使用方法 | Linux 中國
望名生義。uptime 命令告訴你係統啟動了(執行了)多長時間。這是語法:-- Himanshu Arora 本文導航◈ Linux uptime 命令 09%◈ Q1、怎樣使用 uptime 命令 22%◈ Q2、怎樣以更人
學好這五個PPT技巧,讓你老闆刮目相看
在職場中PPT在辦公中使用頻率非常高,但製作一份好的PPT製作也是非常繁瑣。如何讓你快速做好PPT,這幾大技巧你非學不可。 一、文字圖片快速對齊 在PPT製作過程中,文字沒有一定的排版讓人感覺很不舒服,獨個對齊又非常繁瑣,教你一招快速對齊技巧。 操作步驟:選出你所要的文字圖片,點選【格式】-【對齊】
學習Python中,注重這8個小細節,讓你在大資料領域事半功倍
每個人都會遇到這個問題。 學習資料科學的過程,從來就不是一帆風順的。在寫程式碼的時候,你是否也經常不得不反覆搜尋同一個問題,同一個概念,甚至同一個語法結構的特性呢?對,你不是一個人在戰鬥。 我也一直在同樣的情況裡掙扎著。 雖然遇到問題上 StackOverflow 搜一
學會這5個Excel技巧,讓你拒絕加班
在網上,隨處都可以看到Excel技巧,估計已看膩了吧?但下面5個Excel技巧會讓你相見恨晚。關鍵的是它們個個還很實用 圖一
學完這 4 個小技巧,讓你的移動端互動體驗更加優秀
現在在手機等移動端裝置訪問的人越來越多,我們前端開發者一直致力於將設計稿還原成頁面,供使用者訪問。但除高度還原設計稿外,互動上的良好體驗也是我們應該做到的。 ![玩玩手機](https://img2020.cnblogs.com/blog/443443/202103/443443-2021030315184
面趣 | 蘋果最刁鑽的20個面試題,試試你會通關嗎?
如同大部分矽谷公司一樣,蘋果會在面試中詢問基於你過去工作經歷的技術難題,以及與技術不太相關的“燒腦”謎題。有一些需要複雜的數學計算;還有一些雖然簡單,但是語義卻模糊不清。 準備好了麼?我們開始吧! 以下是20道技術類職位題目! 1、
java 效能優化:35 個小細節,讓你提升 java 程式碼的執行效率
前言 程式碼 優化 ,一個很重要的課題。可能有些人覺得沒用,一些細小的地方有什麼好修改的,改與不改對於程式碼的執行效率有什麼影響呢?這個問題我是這麼考慮的,就像大海里面的鯨魚一樣,它吃一條小蝦米有用嗎?沒用,但是,吃的小蝦米一多之後,鯨魚就被餵飽了。 程式碼優化也是一樣,如果專案著眼於儘快無BUG上線,那
Java 15 正式釋出, 14 個新特性,重新整理你的認知!!
JDK 15 2020/09/15 如期而至! 這個時間牛逼啊,和蘋果釋出會同天? ![](https://img2020.cnblogs.com/other/1218593/202009/1218593-20200917085028303-927709450.png) OracleJDK 15 釋出地
揭秘:技術人突破瓶頸期,離不開這幾個關鍵點
設置 良好的 發展 ring demo 號稱 有關 esp 技術交流群 作為一個技術人,不知道你有沒有遇到過下面的情況?“我學不到新東西”“我感覺沒啥成長”“每天都在重復勞動”……其實,每個技術從業者,多多少少都會遇到工作或學習瓶頸期。產生瓶頸期,往往不是因為我們不夠努力,
《惢客創業日記》2018.09.14 週五 “不甘心”這三個字,讓他去了美團。
今天,有一個8年前的同事加我微信好友,真是感謝自己的手機號一直沒變,還能讓想找我的同事找到我。雖然只在微信裡簡單的聊了十幾句,聊的也只是扒一扒曾經一起共事的經歷和彼此的境遇,但是這種“知己重逢”的欣喜感始終沒有消褪,且彼此都感覺到雙方都在為自己心中的那個小小“不甘心”默默打拼。如今,他在上海美團
深度學習引數怎麼調優,這12個trick告訴你
1:優化器。機器學習訓練的目的在於更新引數,優化目標函式,常見優化器有SGD,Adagrad,Adadelta,Adam,Adamax,Nadam。其中SGD和Adam優化器是最為常用的兩種優化器,SGD根據每個batch的資料計算一次區域性的估計,最小化代價函式。 學習速率決定了每次步進的大小
你居然不知道PPT還隱藏這4個神技巧,難怪做起來效率慢
PPT有人天天用,但還是有很多操作不會,甚至不知道,導致做起來的效率低下。今天小編就來說一下4個神技巧,助你提升效率。 1、PPT上的虛線框 在開啟PPT的時候,不想要讓PPT上的虛線框出現怎麼辦?換個版式唄。 操作方法:【ctrl+A】全選幻燈片-【版式】-【空白】。 2、刪除圖片底
快速開發android,離不開這10個優秀的開源專案
作為一名菜雞Android,時常瞻仰大佬們的開源專案是非常必要的。這裡我為大家收集整理了10個優秀的開源專案,方便我們日常開發中學習! 作者:ListenToCode部落格:https://www.jianshu.com/p/9742ce36b6a8 KnowWeather GitHub
開發團隊到底好不好,這 12 個問題能檢驗出來嗎?
【伯樂線上導讀】:「Joel 測試」是 Joel Spolsky 在 2000 年提出的 12 個問題,用來檢驗一個團隊是否是好的開發團隊。17 年過去了,Dale Myers 重新審視了這12個問題,並與時俱進地提出了修改建議。 就在 2013 年,我參加了一個
學習Python爬蟲,你不安裝這幾個常用的庫?
1:urllib urllib.request 這兩個庫是python自帶的庫,不需要重新安裝 在python中輸入如下程式碼: 返回結果為HTTPResponse的物件: <http.client.HTTPResponse obje
《惢客創業日記》2018.09.14 週五 “不甘心”這三個字,讓他去了美團。
今天,有一個8年前的同事加我微信好友,真是感謝自己的手機號一直沒變,還能讓想找我的同事找到我。雖然只在微信裡簡單的聊了十幾句,聊的也只是扒一扒曾經一起共事的經歷和彼此的境遇,但是這種“知己重逢”的欣喜感始終沒有消褪,且彼此都感覺到雙方都在為自己心中的那個小小“不甘心”
太高效了!玩了這麼久的Linux,居然不知道這7個終端快捷鍵!
大家好,我是良許。 作為 Linux 使用者,大家肯定在 Linux 終端下敲過無數的命令。有的命令很短,比如:`ls` 、`cd` 、`pwd` 之類,這種命令大家毫無壓力。但是,有些命令就比較長了,比如: ``` $ git push origin HEAD:refs/for/master ```