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
这个时候所实现的链表数据的增加以及链表数据的删除考虑到了性能的问题,同时也考虑到输出的标准化问题