GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎?java
GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎?git
GitHub 2.5k Star 的Java工程師成神之路 ,真的肯定不來了解一下嗎?程序員
本文從 Java 編譯原理角度,深刻字節碼及 class 文件,抽絲剝繭,瞭解 Java 中的語法糖原理及用法,幫助你們在學會如何使用 Java。github
語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家 Peter.J.Landin 發明的一個術語,指在計算機語言中添加的某種語法,這種語法對語言的功能並無影響,可是更方便程序員使用。簡而言之,語法糖讓程序更加簡潔,有更高的可讀性。數據庫
有意思的是,在編程領域,除了語法糖,還有語法鹽和語法糖精的說法,篇幅有限這裏不作擴展了。編程
咱們所熟知的編程語言中幾乎都有語法糖。做者認爲,語法糖的多少是評判一個語言夠不夠牛逼的標準之一。不少人說Java是一個「低糖語言」,其實從Java 7開始Java語言層面上一直在添加各類糖,主要是在「Project Coin」項目下研發。儘管如今Java有人仍是認爲如今的Java是低糖,將來還會持續向着「高糖」的方向發展。api
前面提到過,語法糖的存在主要是方便開發人員使用。但其實,Java虛擬機並不支持這些語法糖。這些語法糖在編譯階段就會被還原成簡單的基礎語法結構,這個過程就是解語法糖。數組
說到編譯,你們確定都知道,Java語言中,javac
命令能夠將後綴名爲.java
的源文件編譯爲後綴名爲.class
的能夠運行於Java虛擬機的字節碼。若是你去看com.sun.tools.javac.main.JavaCompiler
的源碼,你會發如今compile()
中有一個步驟就是調用desugar()
,這個方法就是負責解語法糖的實現的。緩存
Java 中最經常使用的語法糖主要有泛型、變長參數、條件編譯、自動拆裝箱、內部類等。本文主要來分析下這些語法糖背後的原理。一步一步剝去糖衣,看看其本質。安全
前面提到過,從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類型值,這個過程叫作拆箱。由於這裏的裝箱和拆箱是自動進行的非人爲轉換,因此就稱做爲自動裝箱和拆箱。原始類型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]);
}
複製代碼
從反編譯後代碼能夠看出,可變參數在被使用的時候,他首先會建立一個數組,數組的長度就是調用該方法是傳遞的實參的個數,而後再把參數值所有放到這個數組當中,而後再把這個數組做爲參數傳遞到被調用的方法中。
PS:反編譯後的print方法聲明中有一個transient標識,是否是很奇怪?transient不是不能夠修飾方法嗎?transient不是和序列化有關麼?transient在這裏的做用是什麼?由於這個與本文關係不大,這裏不作深刻分析了。相瞭解的同窗能夠關注我微信公衆號或者博客。
Java SE5提供了一種新的類型-Java的枚舉類型,關鍵字enum
能夠將一組具名的值的有限集合建立爲一種新的類型,而這些具名的值能夠做爲常規的程序組件使用,這是一種很是有用的功能。
要想看源碼,首先得有一個類吧,那麼枚舉類型究竟是什麼類呢?是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
。當咱們嘗試對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語句實現的。其原理也是Java語言的語法糖。根據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循環(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循環和迭代器。
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表達式,有人可能會有質疑,由於網上有人說他並非語法糖。其實我想糾正下這個說法。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的方式。
1、當泛型遇到重載 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,擦除動做致使這兩個方法的特徵簽名變得如出一轍。
2、當泛型遇到catch 泛型的類型參數不能用在Java異常處理的catch語句中。由於異常處理是由JVM在運行時刻來進行的。因爲類型信息被擦除,JVM是沒法區分兩個異常類型MyException<String>
和MyException<Integer>
的
3、當泛型內包含靜態變量
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 class BoxingTest {
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。
只適用於自動裝箱。使用構造函數建立對象不適用。
ConcurrentModificationException
for (Student stu : students) {
if (stu.getId() == 2)
students.remove(stu);
}
複製代碼
會拋出ConcurrentModificationException
異常。
Iterator是工做在一個獨立的線程中,而且擁有一個 mutex 鎖。 Iterator被建立以後會創建一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,因此當索引指針日後移動的時候就找不到要迭代的對象,因此按照 fail-fast 原則 Iterator 會立刻拋出java.util.ConcurrentModificationException
異常。
因此 Iterator
在工做的時候是不容許被迭代的對象被改變的。但你可使用 Iterator
自己的方法remove()
來刪除對象,Iterator.remove()
方法會在刪除當前迭代對象的同時維護索引的一致性。
前面介紹了12種Java中經常使用的語法糖。所謂語法糖就是提供給開發人員便於開發的一種語法而已。可是這種語法只有開發人員認識。要想被執行,須要進行解糖,即轉成JVM認識的語法。當咱們把語法糖解糖以後,你就會發現其實咱們平常使用的這些方便的語法,其實都是一些其餘更簡單的語法構成的。
有了這些語法糖,咱們在平常開發的時候能夠大大提高效率,可是同時也要避免過渡使用。使用以前最好了解下原理,避免掉坑。
參考資料: Java的反編譯 Java中的Switch對整型、字符型、字符串型的具體實現細節 深度分析Java的枚舉類型—-枚舉的線程安全性及序列化問題 Java的枚舉類型用法介紹 Java中的加強for循環(for each)的實現原理與坑 Java中泛型的理解 Java中整型的緩存機制 Java中的可變參數