本篇內容主要講解“Java中的語法糖有哪些及怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java中的語法糖有哪些及怎么使用”吧!
switch支持String和枚舉
泛型
自動裝箱與拆箱
方法變長參數
枚舉
內部類
條件編譯
斷言
數值字面量
for-each
try-with-resource
Lambda表達式
字符串+號。
switch對枚舉和String的支持原理其實差不多。switch原生支持只能支持比較整數類型。如果switch后面是String類型的話,編譯器會將其轉換成String的hashCode的值,所以其實比較的是String的hashCode值。如果switch后面是Enum的話,編譯器會將其轉換為這個枚舉定義的下標(ordinal)。其實最后都是比較的整數類型。下面以Stirng舉個列子。
源代碼
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方法比較進行安全檢查,這個檢查是必要的,因為哈??赡軙l生碰撞。因此它的性能是不如使用枚舉進行switch或者使用純整數常量。
在JDK1.5中,Java語言引入了泛型機制。但是這種泛型機制是通過類型擦除來實現的,即Java中的泛型只在程序源代碼中有效(源代碼階段提供類型檢查),在編譯后的字節碼中自動用強制類型轉換進行替代。也就是說,Java語言中的泛型機制其實就是一顆語法糖,相較與C++、C#相比,其泛型實現實在是不那么優雅。
/** * 在源代碼中存在泛型 */ public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>(); map.put("hello","你好"); String hello = map.get("hello"); System.out.println(hello); }
編譯后的代碼
public static void main(String[] args) { HashMap map = new HashMap(); //類型擦除 map.put("hello", "你好"); String hello = (String)map.get("hello");//強制轉換 System.out.println(hello); }
通過上面反編譯后的代碼我們發現虛擬機中其實是沒有泛型的,只有普通類和普通方法,所有泛型類的類型參數在編譯時都會被擦除,泛型類并沒有自己獨有的Class類對象。
我們知道在Java中的8個基本類型和對應的包裝類型之間是可以互相賦值的(這個過程叫裝箱、拆箱過程)。其實這背后的原理是編譯器做了優化。如下面代碼,將基本類型賦值給包裝類其實是調用了包裝類的valueOf()方法創建了一個包裝類再賦值給了基本類型。而包裝類賦值給基本類型就是調用了包裝類的xxxValue()方法拿到基本數據類型再賦值的。
public static void main(String[] args) { Integer a = 1; int b = 2; int c = a + b; System.out.println(c); }
編譯后的代碼
public static void main(String[] args) { Integer a = Integer.valueOf(1); // 自動裝箱 byte b = 2; int c = a.intValue() + b;//自動拆箱 System.out.println(c); }
變長參數特性是在JDK1.5中引入的,使用變長參數有兩個條件,一是變長的那一部分參數具有相同的類型,二是變長參數必須位于方法參數列表的最后面。變長參數同樣是Java中的語法糖,其內部實現是編譯器在編譯源代碼的時候將變長參數部分轉換成了Java數組。
java中類的定義使用class,枚舉類的定義使用enum。在Java的字節碼結構中,其實并沒有枚舉類型,枚舉類型只是一個語法糖,在編譯完成后被編譯成一個普通的類,也是用Class修飾。這個類繼承java.lang.Enum,并被final關鍵字修飾。
public enum Fruit { APPLE,ORINGE }
將Fruit的class文件進行反編譯
//繼承java.lang.Enum并聲明為final public final class Fruit extends Enum { public static Fruit[] values() { return (Fruit[])$VALUES.clone(); } public static Fruit valueOf(String s) { return (Fruit)Enum.valueOf(Fruit, s); } private Fruit(String s, int i) { super(s, i); } //枚舉類型常量 public static final Fruit APPLE; public static final Fruit ORANGE; private static final Fruit $VALUES[];//使用數組進行維護 static { APPLE = new Fruit("APPLE", 0); ORANGE = new Fruit("ORANGE", 1); $VALUES = (new Fruit[] { APPLE, ORANGE }); } }
通過上面反編譯的代碼,我們可以知道當我們使用enmu來定義一個枚舉類型的時候,編譯器會自動幫我們創建一個final類型的類繼承Enum類,所以枚舉類型不能被繼承。
Java語言中之所以引入內部類,是因為有些時候一個類只想在一個類中有用,我們不想讓其在另外一個地方被使用。內部類之所以是語法糖,是因為其只是一個編譯時的概念,一旦編譯完成,編譯器就會為內部類生成一個單獨的class文件,名為outer$innter.class。
public class Outer { class Inner{ } }
使用javac編譯后,生成兩個class文件Outer.class和Outer$Inner.class,其中Outer$Inner.class的內容如下:
class Outer$Inner { Outer$Inner(Outer var1) { this.this$0 = var1; } }
一般情況下,源程序中所有的行都參加編譯。但有時希望對其中一部分內容只在滿足一定條件下才進行編譯,即對一部分內容指定編譯條件,這就是“條件編譯”(conditional compile)。
Java中的條件編譯是通過編譯器的優化原則實現的:
如果if的條件是false,則在編譯時忽略這個if語句。
忽略未使用的變量。
public class ConditionalCompilation02 { public static void main(String[] args) { if(CompilationConfig.DEBUG_MODE) { System.out.println("[DEBUG MODE]You can print sth"); } else { System.out.println("[RELEASE MODE]You can print sth"); } } }
所以,Java語法的條件編譯,是通過判斷條件為常量的if語句實現的。根據if判斷條件的真假,編譯器直接把分支為false的代碼塊消除。通過該方式實現的條件編譯,必須在方法體內實現,而無法在正整個Java類的結構或者類的屬性上進行條件編譯。
在Java中,assert關鍵字是從JAVA SE 1.4 引入的,為了避免和老版本的Java代碼中使用了assert關鍵字導致錯誤,Java在執行的時候默認是不啟動斷言檢查的(這個時候,所有的斷言語句都將忽略?。?。
如果要開啟斷言檢查,則需要用開關-enableassertions或-ea來開啟。
其實斷言的底層實現就是if語言,如果斷言結果為true,則什么都不做,程序繼續執行,如果斷言結果為false,則程序拋出AssertError來打斷程序的執行。
Java中支持如下形式的數值字面量
十進制:默認的
八進制:整數之前加數字0來表示
十六進制:整數之前加"0x"或"0X"
二進制(新加的):整數之前加"0b"或"0B"
另外在在jdk7中,數值字面量,不管是整數還是浮點數,都允許在數字之間插入任意多個下劃線。這些下劃線不會對字面量的數值產生影響,目的就是方便閱讀。比如:
1_500_000
5_6.3_4
89_3___1
下劃線只能出現在數字中間,前后必須是數字。所以“_100”、“0b_101“是不合法的,無法通過編譯。這樣限制的動機就是可以降低實現的復雜度。有了這個限制,Java編譯器只需在掃描源代碼的時候將所發現的數字中間的下劃線直接刪除就可以了。如果不添加這個限制,編譯器需要進行語法分析才能做出判斷。比如:_100,可能是一個整數字面量100,也可能是一個變量名稱。這就要求編譯器的實現做出更復雜的改動。
增強for循環的對象要么是一個數組,要么實現了Iterable接口。這個語法糖主要用來對數組或者集合進行遍歷,其在循環過程中不能改變集合的大小。增強for循環主要使代碼更加簡潔,其背后的原理是編譯器將增強for循環轉換成了普通的for循環。
public static void main(String[] args) { String[] params = new String[]{"hello","world"}; //增強for循環對象為數組 for(String str : params){ System.out.println(str); } List<String> lists = Arrays.asList("hello","world"); //增強for循環對象實現Iterable接口 for(String str : lists){ System.out.println(str); } }
編譯器編譯后的代碼
public static void main(String[] args) { String[] params = new String[]{"hello", "world"}; String[] lists = params; int var3 = params.length; //數組形式的增強for退化為普通for for(int str = 0; str < var3; ++str) { String str1 = lists[str]; System.out.println(str1); } List var6 = Arrays.asList(new String[]{"hello", "world"}); Iterator var7 = var6.iterator(); //實現Iterable接口的增強for使用iterator接口進行遍歷 while(var7.hasNext()) { String var8 = (String)var7.next(); System.out.println(var8); } }
當一個外部資源的句柄對象實現了AutoCloseable接口,JDK7中便可以利用try-with-resource語法更優雅的關閉資源,消除板式代碼。
public static void main(String[] args) { try (FileInputStream inputStream = new FileInputStream(new File("test"))) { System.out.println(inputStream.read()); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } }
將外部資源的句柄對象的創建放在try關鍵字后面的括號中,當這個try-catch代碼塊執行完畢后,Java會確保外部資源的close方法被調用。代碼是不是瞬間簡潔許多!try-with-resource并不是JVM虛擬機的新增功能,只是JDK實現了一個語法糖,當你將上面代碼反編譯后會發現,其實對JVM虛擬機而言,它看到的依然是之前的寫法:
public static void main(String[] args) { try { FileInputStream inputStream = new FileInputStream(new File("test")); Throwable var2 = null; try { System.out.println(inputStream.read()); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if (inputStream != null) { if (var2 != null) { try { inputStream.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { inputStream.close(); } } } } catch (IOException var14) { throw new RuntimeException(var14.getMessage(), var14); } }
其實背后的原理也很簡單,那些我們沒有做的關閉資源的操作,編譯器都幫我們做了。
Lambda表達式雖然看著很先進,但其實Lambda表達式的本質只是一個"語法糖",由編譯器推斷并幫你轉換包裝為常規的代碼,因此你可以使用更少的代碼來實現同樣的功能。本人建議不要亂用,因為這就和某些很高級的黑客寫的代碼一樣,簡潔,難懂,難以調試,維護人員想罵娘。
lambda表達式允許你通過表達式來代替功能接口。Lambda表達式還增強了集合庫。 Java SE 8添加了2個對集合數據進行批量操作的包: java.util.function 包以及java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了許多額外的功能。 總的來說,lambda表達式和 stream 是自Java語言添加泛型(Generics)和注解(annotation)以來最大的變化。
基本語法: (parameters) -> expression 或 (parameters) ->{ statements; }
Lambda表達式的一些簡單列子
// 1. 不需要參數,返回值為 5 () -> 5 // 2. 接收一個參數(數字類型),返回其2倍的值 x -> 2 * x // 3. 接受2個參數(數字),并返回他們的差值 (x, y) -> x – y // 4. 接收2個int型整數,返回他們的和 (int x, int y) -> x + y // 5. 接受一個 string 對象,并在控制臺打印,不返回任何值(看起來像是返回void) (String s) -> System.out.print(s)
String[] atp = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer", "Roger Federer", "Andy Murray", "Tomas Berdych", "Juan Martin Del Potro"}; List<String> players = Arrays.asList(atp); //實現功能接口 players.forEach((String player) ->{ System.out.println(player); }); Runnable runnable = () -> { System.out.println("l am a new thread..."); }; new Thread(runnable).start();
players.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); Comparator<String> comparator = (String o1,String o2) ->{return o1.compareTo(o2);}; players.sort(comparator);
Stream是對集合的包裝,通常和lambda一起使用。 使用lambdas可以支持許多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同樣,Stream使用懶運算,他們并不會真正地讀取所有數據,遇到像getFirst() 這樣的方法就會結束鏈式語法。
String s=null; s=s+"abc"; System.out.println(s);
上面的代碼輸出?
字符串拼接原理:運行時,兩個字符串str1, str2的拼接首先會調用 String.valueOf(obj),這個Obj為str1,而String.valueOf(Obj)中的實現是return obj == null ? “null” : obj.toString(), 然后產生StringBuilder, 調用的StringBuilder(str1)構造方法, 把StringBuilder初始化,長度為str1.length()+16,并且調用append(str1)! 接下來調用StringBuilder.append(str2), 把第二個字符串拼接進去, 然后調用StringBuilder.toString返回結果!
所以答案是:StringBuilder(“null”).append(“abc”).toString(); //nullabc
到此,相信大家對“Java中的語法糖有哪些及怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。