爲什麼說寫 Java 的人 for循環得用好?

Java 中的循環有很多種,但是什麼情況下用哪種,哪種效率高以及每種的特性,相信大多數人沒有去深究過,這裏面的學問可大着哩,一起來看看吧!

Java 循環的 4 種寫法

注意,是四種寫法,並不是說底層的四種實現方式,這四種寫法各有千秋,但是也是最常用的幾種

  1. 普通的 fori 循環
  2. forEach 語法糖
  3. lambda表達式 forEach
  4. 原生迭代器

注意,以下示例的 User 對象源碼如下:

class User {
        private String name;
        private String address;
        private Integer age;

        public User(String name, String address, Integer age) {
            this.name = name;
            this.address = address;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }

普通 fori 循環

普通 for 循環原理很簡單,首先獲取集合的長度 userList.size(),循環體內根據循環到的下標獲取對應的元素, 然後每次循環 +1,達到遍歷整個集合的目的。

這種寫法在以前非常的常見,現在大多使用 forEach 替代。

List<User> userList = new ArrayList<>();
userList.add(new User("同學1", "北京", 10));
userList.add(new User("同學2", "上海", 15));
userList.add(new User("同學3", "廣州", 12));

// 普通 for 循環
for (int i = 0; i < userList.size(); i++) {
    User user = userList.get(i);
    System.out.println(user);
}

輸出:

User{name='同學1', address='北京', age=10}
User{name='同學2', address='上海', age=15}
User{name='同學3', address='廣州', age=12}
User{name='同學1', address='北京', age=10}
User{name='同學2', address='上海', age=15}
User{name='同學3', address='廣州', age=12}

Process finished with exit code 0

但是普通 for 循環有兩個不容忽視的優點。

第一,它在循環過程中可以輕鬆獲取下標,比如我們想在循環中尋找符合條件的下標,那就只能使用 fori 循環,

for (int i = 0; i < userList.size(); i++) {
   User user = userList.get(i);
    if(user.age == 15){
        return i;
    }
}

第二點是它並非迭代器實現,也就是說在循環過程中它可以輕鬆的修改集合內的元素,增刪改都沒有問題,雖然不推薦這樣做,但是這樣的需求在實際開發中還是可能遇到。

int size = userList.size();
// 普通 for 循環
for (int i = 0; i < size; i++) {
    size = userList.size();
    User user = userList.get(i);
    if (user.age == 15) {
        userList.remove(2);
    }
}

forEach循環

For-Each 是 Java5 中引入的另一種數組遍歷技術,它以類似於常規for循環的關鍵字開頭具有以下特點:

  1. 無需聲明和初始化循環計數器變量,而是聲明一個與數組的基本類型相同類型的變量,然後是冒號,然後是冒號,然後是數組名。
  2. 在循環主體中,可以使用創建的循環變量,而不是使用索引數組元素。
  3. 它通常用於遍歷數組或Collections類(例如ArrayList)

語法

for (type var : array) 
{ 
    statements using var;
}

示例

for (int i=0; i<arr.length; i++) 
{ 
    type var = arr[i];
    statements using var;
}

應用到 fori 的例子

for (User user : userList) {
   System.out.println(user);
}

輸出

User{name='同學1', address='北京', age=10}
User{name='同學2', address='上海', age=15}
User{name='同學3', address='廣州', age=12}
User{name='同學1', address='北京', age=10}
User{name='同學2', address='上海', age=15}
User{name='同學3', address='廣州', age=12}

Process finished with exit code 0

侷限性:

當你想要在循環體內修改數組時,for-each 循環不合適,你應該選擇普通 fori 循環

for (int num : marks) 
{
    // only changes num, not the array element
    num = num*2; 
}

forEach 不跟蹤索引,內部使用迭代器實現,所以我們在循環過程中沒辦法獲取到索引

for (int num : numbers) {
    if (num == target) {
        return ???;   // do not know the index of num
    }
}
For - each only iterates forward over the array in single steps
// cannot be converted to a for-each loop
for (int i = numbers.length - 1; i > 0; i--) {
    System.out.println(numbers[i]);
}
For - each cannot process two decision making statements at once
// cannot be easily converted to a for-each loop
for (int i = 0; i < numbers.length; i++) {
    if (numbers[i] == arr[i]) { ...
    }
}

lambda 表達式 forEach

userList.forEach(e -> {
	System.out.println(e);
});

這種寫法相比 forEach 更加的簡單,但是存在一個很麻煩的問題,由於 lambda 是基於內部類實現的,所以我們在循環體內如果想修改外部變量,比如這樣

int i = 0;
userList.forEach(e -> {
    System.out.println(e);
    i++;
});

代碼中的 i++ 就會報錯,因爲內部類無法直接訪問外部資源,Variable used in lambda expression should be final or effectively final,需要我們將變量修改爲 Atomic ,如下:

AtomicInteger i = new AtomicInteger();
userList.forEach(e -> {
    System.out.println(e);
    i.set(i.getAndIncrement()+1);
});

是不是很蛋疼哩~

迭代器 iterator

迭代器在現在實際開發中使用比較少了,它長這個樣子,其實 forEach 的底層就是迭代器實現。

  • forEach 中對於list編譯器會調用 Iterable 接口的 iterator 方法來循環遍歷數組的元素,iterator方法中是調用Iterator接口的的 next() 和 hasNext() 方法來做循環遍歷。java中有一個叫做迭代器模式的設計模式,這個其實就是對迭代器模式的一個實現。

  • 對於數組,就是轉化爲對數組中的每一個元素的循環引用

Iterator<User> iterator = userList.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

執行結果

User{name='同學1', address='北京', age=10}
User{name='同學2', address='上海', age=15}
User{name='同學3', address='廣州', age=12}

Process finished with exit code 0

好了,關於 Java 中我瞭解的循環的相關內容就講完了,如果對你有幫助,可以關注我,我會不定期發一些個人比較瞭解的技術內容。

ps: 本文中如果您發現錯誤的地方,請私信或者評論指出,感謝!

歡迎關注我的微信公衆號:代碼宇宙

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章