Java集合--集合輸出

內容學習於:edu.aliyun.com


引言

  至此爲止已經實現了List 與Set兩個集合數據的內容存儲,但是對於所有的單值存儲集合,其存儲數據的核心目的在於“輸出”,但是對於集合的輸出並不是說將其轉換爲對象數組利用循環的形式完成,它有着自己的輸出要求,在集合裏面針對於輸出的操作實際上有四種模式: Iterator (90%)、ListIterator ( 1%)、Enumeration (2%)、foreach(7%)

1. Iterator迭代輸出

  “迭代”是一種循環結構的另類稱呼,所謂的迭代可以簡單的理解爲,在若干個數據上逐個進行判斷,如果有數據則進行內容的輸出,在Collection接口裏面實現了一個Iterable接口,實際上這個接口裏面就規定了一個獲取Iterator接口實例的操作方法,這個接口裏面有兩個核心的操作方法。

  • 消費處理:default void forEach(Consumer<? super T> action)
  • 獲取Iterator接口實例Iterator iterator()

  Iterator是java類集之中定義的集合數據的標準輸出接口,在Iterator接口裏面定義有如下的幾個方法:

  • 判斷是否有下一個內容:boolean hasNext()
  • 獲取當前內容:E next()
  • 刪除當前元素:default void remove()

  只要是Collection接口的子接口或者是子類都可以直接利用iterator()方法獲取Iterator接口實例,繼承關係如下。在這裏插入圖片描述

實現集合內容的輸出代碼:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Set<String> data = Set.of("Hello","www.mldn.cn","MLDN");
        Iterator<String> iterator = data.iterator();//獲取Iterator實例化對象
        while (iterator.hasNext()){//知道結束條件,不知道循環次數
            System.out.println(iterator.next());
        }

    }
}

結果:

Hello
www.mldn.cn
MLDN

  既然Set接口可以使用,那麼對於List和Collection的接口就全部都可以使用。

面試題: List 接口中存在有get0方法,可以根據索引獲取數據,那麼請問使用這種方式結合循環輸出和使用Iterator有那些不同?

  • List相比較Set和Collection集合來講,最大的操作特點是支持有get()方法,可以依據索引獲取相應的數據內容。

範例:通過索引訪問List 集合代碼:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        List<String> data = List.of("Hello","www.mldn.cn","MLDN");
        Iterator<String> iterator = data.iterator();//獲取Iterator實例化對象
        for (int i = 0 ;i<data.size();i++){
            System.out.println(data.get(i));
        }
    }
}

結果:

Hello
www.mldn.cn
MLDN

  迭代操作的特點是隻進行一次循環處理,但是如果使用了get()方法呢,那麼每一次都需要進行索引數值的匹配,那麼最終得到的時間複雜度很高,所以性能一定不高。

  但是在Iterator接口裏面有一一個 remove0方法,那麼它與Collction接口中定義的remove()有那些聯繫呢?

Iterator刪除的代碼:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        List<String> data = new ArrayList<>();
        data.add("aaaa");
        data.add("bbbb");
        data.add("cccc");
        data.add("dddd");
        Iterator<String> iterator = data.iterator();//獲取Iterator實例化對象
        while (iterator.hasNext()){
            String str = iterator.next();
            if ("bbbb".equals((str))){
                iterator.remove();
            }else {
                System.out.println(str);
            }
        }
    }
}

結果:

aaaa
cccc
dddd

集合刪除的代碼:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        List<String> data = new ArrayList<>();
        data.add("aaaa");
        data.add("bbbb");
        data.add("cccc");
        data.add("dddd");
        Iterator<String> iterator = data.iterator();//獲取Iterator實例化對象
        while (iterator.hasNext()){
            String str = iterator.next();
            if ("bbbb".equals((str))){
                data.remove(str);
            }else {
                System.out.println(str);
            }
        }
    }
}

結果:

aaaa
Exception in thread “main” java.util.ConcurrentModificationException

  實際上使用集合中的刪除操作,在迭代的時候是無法提供正常支持的,所以纔在Iterator接口之中追加有一個remove()方法。
  從JDK 1.8 開始由於追加了Lambda表達式,以及方法引用和功能性的接口,所以如果現在只是想進行內容的輸出,實際上比較簡單。

foreach輸出代碼:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        List<String> data = new ArrayList<>();
        data.add("aaaa");
        data.add("bbbb");
        data.add("cccc");
        data.add("dddd");
        data.forEach(System.out::println);
    }
}

結果:

aaaa
bbbb
cccc
dddd

  此輸出的操作只是屬於一種簡化的處理形式,實際之中的輸出依然是通過迭代獲取每一個數據,因爲有可能需要進行數據的相關處理操作。

2. ListIterator雙向迭代

  Iterator接口最爲重要的操作特點是可以進行單向迭代處理,即,所有的數據只能夠由前向後進行輸出,但是在一些特殊的情況下有可能會使用到雙向迭代操作(可以由前向後,也可以由後向前),所以此時就必須使用到Iterator子接口“ListIterator",但是需要記住一個最爲核心的問題,Collection 接口只定義了實例化Iterator接口對象的方法,但是並未定義實例化Listerator接口實例化方法,而List子接口定義(方法:“ public ListIterator listIterator()")
  如下圖所示:在這裏插入圖片描述

  在ListIterator接口裏面提供有兩個處理方法:

  • 判斷是否有上一個內容: public boolean hasPrevious();
  • 獲取內容: public E previous()。

代碼:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        List<String> data = new ArrayList<>();
        data.add("aaaa");
        data.add("bbbb");
        data.add("cccc");
        data.add("dddd");
        ListIterator<String> iterator =  data.listIterator();
        System.out.println("逆序迭代:");
        while (iterator.hasPrevious()){
            System.out.println(iterator.previous());
        }
        System.out.println("正序迭代:");
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

結果:

逆序迭代:
正序迭代:
aaaa
bbbb
cccc
dddd

  但是一定要記住,Listerator 之所以可以實現雙向迭代是因爲其內部維護了一個操作的指針,如果是正向迭代,則指針由前指向後,如果是反向迭代,則會進行逆序的指針處理。如果指針不指向最後,則無法逆序輸出。

3. Enumeration枚舉輸出

  在JDK1.0的時候爲了方便Vector集合的輸出,在Enumeration接口最初的時候只定義有兩個操作方法。

  • 判斷是否有下一個元素:boolean hasMoreElements()
  • 獲取當前元素:E nextElement()

  Iterator接口與Enumeration接口的核心功能非常相似,但是優點在於: Iterator 是個標準,並且方法名稱簡短,但是需要注意的是,並沒有任何一個集合的接口擁有獲取Enumeration 對象的方法,只有Vector 這個古老的類中才有對應的方法:

  • 獲取Enumeration實例: public Enumeration elements(); ;
  • 獲取Enumeration對象:public Enumeration elements()

  繼承結構如下圖所示:在這裏插入圖片描述

  這種輸出操作畢竟屬於古老的輸出形式了,除了少部分的人員還可能使用之外,其它的地方都不建議過多的出現,但是考慮到代碼的編寫問題,還是應該儘可能的把這些方法全部記住。

4. foreach輸出

  在JDK1.5之後追加了foreach輸出操作,但是這個輸出的操作不僅僅只是在數組上可以使用,在類集上也可以使用它進行迭代的輸出操作。

代碼:

class Message implements Iterable<String>{

    private String [] content = new String[]{"aaaa","bbbbb","cccc"};
    private int foot = 0;
    @Override
    public Iterator<String> iterator() {
        return new MessageIter() ;
    }
    private class MessageIter implements Iterator{

        @Override
        public boolean hasNext() {
            return Message.this.foot<Message.this.content.length;
        }

        @Override
        public Object next() {
            return Message.this.content[Message.this.foot++];
        }
    }
}
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Message msg = new Message();
        for (String t:msg){//自定義類對象
            System.out.println(t);
        }
    }
}

結果:

aaaa
bbbbb
cccc

  以上的操作使用了和數組對等的結構實現了內容的輸出,其實從本質上來講,foreach結構是一種擴展結構,並且實際之中也很少會有這樣的擴展,不過有一個問題會出現。
  如下圖所示:在這裏插入圖片描述

面試題:請問如何可以將一個自定義的類利用foreach輸出?

  • 如果現在自定義的類對象要想實現foreach輸出,則這個對象所在的類一定要實現Iterable接口。

代碼:

class Message implements Iterable<String> {

    private String[] content = new String[]{"aaa", "bbb", "ccc"};

    private int foot = 0;

    private class MessageIter implements Iterator<String> {

        @Override
        public boolean hasNext() {
            return Message.this.foot < Message.this.content.length;
        }

        @Override
        public String next() {
            return Message.this.content[Message.this.foot++];
        }
    }

    @Override
    public Iterator<String> iterator() {
        return new MessageIter();
    }
}

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Message msg = new Message();
        for (String t : msg) {//自定義類對象
            System.out.println(t);
        }
    }
}

結果:

aaa
bbb
ccc

  那麼既然現在已經對輸出有了標準性的規定,那麼實際上應用這種操作最有價值的地方就在於自定義的鏈表結構上了。

代碼:

interface ILink<T> extends Iterable<T>{
    public void add(T e);
}

class LinkImpl<T> implements ILink<T>{


    private class Node{
        private T data;
        private Node next;
        public Node(T e){
            this.data = e;
        }
    }

    private Node  root;
    private Node  last;
    private Node currentNode;

    @Override
    public void add(T e) {
        Node newNode = new Node(e);
        if (this.root == null){//如果根節點沒有數據
            this.root = newNode;
        }else {
            this.last.next = newNode;//往後加數據
        }
        this.last = newNode;
    }

    @Override
    public Iterator<T> iterator() {
        this.currentNode = this.root;
        return new LinkIter();
    }

    private class LinkIter implements Iterator<T>{

        @Override
        public boolean hasNext() {
            return LinkImpl.this.currentNode!=null;
        }

        @Override
        public T next() {
            T data = LinkImpl.this.currentNode.data;
            LinkImpl.this.currentNode = LinkImpl.this.currentNode.next;
            return data;
        }
    }
}

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        LinkImpl<String>  link  = new LinkImpl<>();
        link.add("aaaa");
        link.add("bbbb");
        link.add("cccc");
        for (String t:link){
            System.out.println(t);
        }
    }
}

結果:

aaaa
bbbb
cccc

  這個時候所實現的鏈表數據的增加以及鏈表數據的刪除考慮到了性能的問題,同時也考慮到輸出的標準化問題

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