End

集合 enum 枚举 迭代 Iterator

本文地址


目录

集合 enum 枚举 迭代 Iterator

Enum 类

这是所有 Java 语言枚举类型的公共基本类。

public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable

构造方法

protected Enum(String name, int ordinal) 单独的构造方法。

  • 程序员无法调用此构造方法。
  • 该构造方法用于由响应枚举类型声明的编译器发出的代码。
  • 参数 name - - 此枚举常量的名称,它是用来声明该常量的标识符。
  • 参数 ordinal - - 枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。

静态方法

static <T extends Enum<T>>  T  valueOf(Class<T> enumType, String name)

返回带指定名称的指定枚举类型的枚举常量。

  • 名称必须与在此类型中声明枚举常量所用的标识符完全匹配。不允许使用额外的空白字符。
  • 参数:enumType - 要从中返回常量的枚举类型的 Class 对象
  • 参数:name - 要返回的常量名称
  • 抛出:
    • IllegalArgumentException - 如果指定枚举类型不包含指定名称的常量,或者指定类对象不表示枚举类型
    • NullPointerException - 如果 enumType 或 name 为空

另外两个没有在API文档中出现但实际存在的静态方法:

static T valueOf(String name) //返回带指定名称的当前枚举类型的枚举常量。
static T[] values() //返回当前枚举类型的枚举常量数组。

新增的方法

Class<E>  getDeclaringClass()

返回与此枚举常量的枚举类型相对应的 Class 对象。

  • 当且仅当 e1.getDeclaringClass() == e2.getDeclaringClass() 时,两个枚举常量 e1 和 e2 的枚举类型才相同。
  • 由该方法返回的值不同于由 Object.getClass() 方法返回的值, Object.getClass() 方法用于带有特定常量的类主体的枚举常量。
String  name()

返回此枚举常量的名称,在其枚举声明中对其进行声明。

  • 与此方法相比,大多数程序员应该优先考虑使用 toString() 方法,因为 toString 方法返回更加用户友好的名称。
  • 该方法主要设计用于特殊情形,其正确性取决于获取正确的名称,其名称不会随版本的改变而改变。
int  ordinal()

返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
大多数程序员不会使用此方法。它被设计用于复杂的基于枚举的数据结构,比如 EnumSet 和 EnumMap。

Object 中的方法

int  compareTo(E o)

比较此枚举与指定对象的顺序。

  • 在该对象小于、等于或大于指定对象时,分别返回负整数、零或正整数。
  • 枚举常量只能与相同枚举类型的其他枚举常量进行比较。
  • 该方法实现的自然顺序就是声明常量的顺序。
boolean  equals(Object other) //当指定对象等于此枚举常量时,返回 true。
protected  void  finalize()

枚举类不能有 finalize 方法。
类 Object 中的 finalize 方法的作用是:当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

int  hashCode() //返回枚举常量的哈希码。
String  toString()

返回枚举常量的名称,它包含在声明中。

  • 它包含在声明中。可以重写此方法,虽然一般来说没有必要。
  • 当存在更加“程序员友好的”字符串形式时,应该使用枚举类型重写此方法。
protected Object clone() throws CloneNotSupportedException

返回此实例的一个副本。

  • 如果对象的类不支持 Cloneable 接口,则抛出 CloneNotSupportedException。这可保证永远不会复制枚举,这对于保留其“单元素”状态是必需的。
  • 如果对象的类不支持 Cloneable 接口,则重写 clone 方法的子类也会抛出此异常,以指示无法复制某个实例。

关键字 enum

创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类。枚举类型符合通用模式 Class Enum<E extends Enum>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。

enum 的语法结构尽管和 class 的语法不一样,但是经过编译器编译之后产生的是一个 class 文件。该 class 文件经过反编译可以看到实际上是生成了一个类,该类继承了 java.lang.Enum。比如:

public enum EnumTest {
    MON, TUE;
}

首先编译一下(使用 java 命令),然后直接读取编译后的 EnumTest.class 文件,内容如下:

public enum EnumTest {
    MON,
    TUE,
    private EnumTest() { 
    }
}

经过反编译之后得到的内容如下:

javap EnumTest.class
Compiled from "EnumTest.java"
public final class test.EnumTest extends java.lang.Enum<test.EnumTest> { //继承自 Enum
  public static final test.EnumTest MON;
  public static final test.EnumTest TUE;
  public static test.EnumTest[] values();    //多了一个 values 方法
  public static test.EnumTest valueOf(java.lang.String);    //重载了一个 valueOf 方法
  static {};
}

所以,实际上 enum 就是一个 class,只不过 java 编译器帮我们做了语法的解析和编译而已。

总结:

可以把 enum 看成是一个普通的 class,它们都可以定义一些属性和方法,不同之处是:enum 不能使用 extends 关键字继承其他类,因为 enum 已经继承了 java.lang.Enum (单一继承)。

测试代码

最简单的定义形式

public enum EnumTest {
    MON, TUE, WED, THU, FRI, SAT, SUN; //这段代码实际上调用了7次构造方法 Enum(String name, int ordinal):
}

详细使用案例

给 enum 自定义属性和方法:

enum MyEnum {
    MON("bqt1", 1), TUE("bqt2", 2) {
        @Override
        public String tips() {
            return "黑色星期二";
        }
    },
    WED("bqt3", 3) {
        @Override
        public boolean isBoy() {
            return true;
        }
    },
    THU("bqt1", 1);

    // 成员变量
    public String key;
    public int value;

    // 构造方法
    MyEnum(String key, int value) {
        this.key = key;
        this.value = value;
    }

    public String tips() {
        return "Nothing";
    }

    public boolean isBoy() {
        return false;
    }
}

测试

System.out.println(MyEnum.MON instanceof Enum); // true
System.out.println(MyEnum.valueOf("MON") + "  " + Enum.valueOf(MyEnum.class, "MON")); // MON  MON

//常用方法
System.out.println(MyEnum.MON.name() + "  " + MyEnum.MON.toString()); // MON  MON
System.out.println(MyEnum.MON.ordinal() + "  " + MyEnum.MON.getDeclaringClass()); // 0  class MyEnum
System.out.println(MyEnum.MON.equals(MyEnum.THU) + " " + MyEnum.MON.compareTo(MyEnum.THU)); // false -3

//给 enum 自定义属性和方法
System.out.println(MyEnum.MON.key + "  " + MyEnum.MON.value); // bqt1  1
System.out.println(MyEnum.MON.tips() + "  " + MyEnum.TUE.tips()); // Nothing  黑色星期二
System.out.println(MyEnum.MON.isBoy() + "  " + MyEnum.WED.isBoy()); // false  true

//遍历
MyEnum[] array = MyEnum.values();
System.out.println(Arrays.toString(array)); // [MON, TUE, WED, THU]
for (MyEnum myEnum : MyEnum.values()) {
    System.out.print(myEnum.toString() + "  "); // MON  TUE  WED  THU  
}

在 switch 语句中使用 enum

比如上面的 MyEnum ,如果按下面形式去使用:

MyEnum myEnum = MyEnum.TUE;
switch (myEnum) {
case MyEnum.MON:
    break;
case MyEnum.TUE:
    break;
default:
    break;
}

此时会提示:

an enum switch case label must be the unqualified name of an enumeration constant

意思是: 枚举的 switch case 标签必须为枚举常量的非限定名称。

什么意思呢?意思就是 case 语句中只能写枚举类定义的变量名称,不能加类名。

正常代码如下:

MyEnum myEnum = MyEnum.TUE;
switch (myEnum) {
case MON:
    break;
case TUE:
    break;
default:
    break;
}

为什么必须这么写呢?

我们看 官方文档 中对 switch 语句的描述

  • If the type of the switch statement’s Expression is an enum type, then every case constant associated with the switch statement must be an enum constant of that type.
  • Every case label has a case constant, which is either a constant expression or the name of an enum constant.
  • If the type of the switch statement's语句 Expression is an enum type, then every case constant associated with关联的 the switch statement must be an enum constant of that type.
  • If the switch statement's Expression is of a reference type引用类型, that is, String or a boxed primitive type or an enum type, then an exception will be thrown will occur if the Expression evaluates to null at run time.
  • A Java compiler is encouraged鼓励 (but not required) to provide a warning if a switch on an enum-valued expression lacks缺少 a default label and lacks case labels for one or more of the enum's constants. Such a switch will silently do nothing if the expression evaluates to one of the missing constants.

里面也貌似并没有细说为何不能带限定名。

迭代接口文档

Iterable

public interface java.lang.Iterable<T>

子接口:Collection<E>, Deque<E>, List<E>, Queue<E>, Set<E>, SortedSet<E>

实现这个接口允许对象成为 "foreach" 语句的目标。

定义的方法:

  • Iterator iterator() //返回一个在一组 T 类型的元素上进行迭代的迭代器

Enumeration

public interface Enumeration<E>

实现 Enumeration 接口的对象,它生成一系列元素,一次生成一个。连续调用 nextElement 方法将返回一系列的连续元素。

这些方法主要通过向量的元素、哈希表的键以及哈希表中的值进行枚举。枚举也用于将输入流指定到 SequenceInputStream 中。

注:此接口的功能与 Iterator 接口的功能是重复的。此外,Iterator 接口添加了一个可选的移除操作,并使用较短的方法名。新的实现应该优先考虑使用 Iterator 接口而不是 Enumeration 接口。

定义的方法:

  • boolean hasMoreElements() //测试此枚举是否包含更多的元素。当且仅当此枚举对象至少还包含一个可提供的元素时,才返回 true;否则返回 false
  • E nextElement() //如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。如果没有更多的元素存在,则抛出 NoSuchElementException
Vector<String> vector = new Vector<String>(Arrays.asList("a", "b", "c"));
Enumeration<String> enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
    System.out.println(enumeration.nextElement());
}

注意:

  • 只提供了遍历 VectorHashtable 类型集合元素的功能
  • Hashtable 的子类 Properties 的 propertyNames 方法可以返回所有健的枚举
  • 常用于将【输入流】指定到 SequenceInputStream
  • 集合工具类的静态方法 enumeration(Collection<T>c) 可以返回一个指定集合上的枚举,当需要枚举对象时应优先考虑通过这种方式获取

Iterator

public interface Iterator<E>

对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同:

  • 迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素
  • 方法名称得到了改进

定义的方法:

  • boolean hasNext() //如果仍有元素可以迭代,则返回 true。换句话说,如果 next 返回了元素而不是抛出异常,则返回 true。
  • E next() //返回迭代的下一个元素。如果没有元素可以迭代,抛出 NoSuchElementException。
  • void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
    • 在没调用 next 之前不能调用此方法。每次调用 next 只能调用一次此方法。多次调用 next 方法后也只能调用一次此方法。
    • 如果进行迭代时用调用此方法之外的其他方式修改了该迭代器所指向的 collection,则迭代器的行为是不确定的。
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b"));
Iterator<String> iterator = list.iterator();

System.out.println(iterator.hasNext() + " " + iterator.hasNext() + " " + iterator.hasNext());//true true true
System.out.println(iterator.next() + "  " + iterator.next());//a  b

System.out.println(iterator.hasNext()); //false
System.out.println(iterator.next()); //NoSuchElementException
new ArrayList<String>(Arrays.asList("a", "b")).iterator().remove(); //IllegalStateException
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
Iterator<String> iterator = list.iterator();

System.out.println(iterator.next());//a
iterator.remove();
System.out.println(list);//[b, c]

iterator.next();
iterator.next();
iterator.remove();
System.out.println(list);//[b],移除的是最后一次调用 next 时返回的元素

iterator.remove();//IllegalStateException。虽然之前调用了两次 next,但仍然只能调用一次 remove 方法    }

ListIterator

public interface ListIterator<E> extends Iterator<E>

列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。

ListIterator 没有当前元素;它的光标位置 始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置。

注意,remove() 和 set(Object) 方法不是 根据光标位置定义的,它们是根据对调用 next() 或 previous() 所返回的最后一个元素的操作定义的。

定义的方法:

Iterator 中的方法或类似的方法:

  • boolean hasNext/hasPrevious() 以正向遍历列表时,如果列表迭代器有多个元素,则返回 true。换句话说,如果 next 返回一个元素而不是抛出异常,则返回 true。
  • E next/previous() 返回列表中的下一个元素。
    • 可以重复调用此方法来迭代此列表,或混合调用 previous 来前后移动。
    • 注意交替调用 next 和 previous 将重复返回相同的元素。
    • 如果没有可迭代的下一个元素,则抛出 NoSuchElementException 。
  • void remove() 从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。
    • 对于每个 next 或 previous 调用,只能执行一次此调用。
    • 只有在最后一次调用 next 或 previous 之后,尚未调用 ListIterator.add 时才可以执行该调用。
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
ListIterator<String> iterator = list.listIterator();

System.out.println(iterator.hasPrevious() + "  " + iterator.hasNext() + "  " + iterator.hasPrevious());//false  true  false
System.out.println(iterator.next() + "  " + iterator.next() + "  " + iterator.previous());//a  b  b,交替调用 next 和 previous 将重复返回相同的元素
iterator.remove();
System.out.println(list);//[a, c]

新增的增强功能的方法:

  • void add(E e) 将指定的元素插入列表(可选操作)。
    • 该元素直接插入到 next 返回的下一个元素的前面(如果有),或者 previous 返回的下一个元素之后(如果有)
    • 如果列表没有元素,那么新元素就成为列表中的唯一元素。
    • 新元素被插入到隐式光标前:不影响对 next 的后续调用,并且对 previous 的后续调用会返回此新元素(此调用把调用 nextIndex 或 previousIndex 所返回的值增加 1)。
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
ListIterator<String> iterator = list.listIterator();

iterator.add("add1");//该元素直接插入到 next 返回的下一个元素的前面,而 next返回的下一个元素是 a 
//因为 previous 没有元素,所以不能用 previous 的规则去解释
iterator.add("add2");//该元素直接插入到 next 返回的下一个元素的前面,而 next返回的下一个元素是 a
//同时也可以这样解释:该元素直接插入到 previous 返回的下一个元素之后,而 previous 返回的下一个元素是 add1
System.out.println(list);//[add1, add2, a, b, c]

System.out.println(iterator.next()); //a
System.out.println(iterator.previous() + "  " + iterator.previous());//a  add2
iterator.add("add3");//该元素直接插入到 previous 返回的下一个元素之后,而 previous 返回的下一个元素是 add1
//同时也可以这样解释:该元素直接插入到 next 返回的下一个元素的前面,而 next 返回的下一个元素是 add2
iterator.add("add4");//该元素直接插入到 previous 返回的下一个元素之后,而 previous 返回的下一个元素是刚刚通过 add 添加的元素 add3
//同时也可以这样解释:该元素直接插入到 next 返回的下一个元素的前面,而 next 返回的下一个元素是 add2
System.out.println(list);//[add1, add3, add4, add2, a, b, c]
  • void set(E e) 用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。
    • 只有在最后一次调用 next 或 previous 后既没有调用 ListIterator.remove 也没有调用 ListIterator.add 时才可以进行该调用。
    • 参数:e - 用于替换 next 或 previous 返回的最后一个元素的元素。
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
ListIterator<String> iterator = list.listIterator();

iterator.add("add1");
System.out.println(iterator.next() + "  " + iterator.next()); //a  b
iterator.set("set");
System.out.println(list);//[add1, a, set, c]
  • int nextIndex() 返回对 next 的后续调用所返回元素的索引。如果列表迭代器在列表的结尾,则返回列表的大小。
  • int previousIndex() 返回对 previous 的后续调用所返回元素的索引。如果列表迭代器在列表的开始,则返回 -1
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
ListIterator<String> iterator = list.listIterator();

System.out.println(iterator.nextIndex() + "  " + iterator.nextIndex() + "  " + iterator.previousIndex());//0  0  -1
iterator.add("add1");
System.out.println(iterator.nextIndex() + "  " + iterator.previousIndex());//1  0

System.out.println(iterator.next() + "  " + list); //a  [add1, a, b, c]
System.out.println(iterator.nextIndex() + "  " + iterator.previousIndex());//2  1

System.out.println(iterator.previous() + "  " + iterator.previous());//a  add1
System.out.println(iterator.nextIndex() + "  " + iterator.previousIndex());//0  -1

示例代码

各种姿势测试 add

在集合的最前面按顺序添加若干元素

List<String> list1 = new ArrayList<>(Arrays.asList(new String[] { "a", "b", "c" }));
ListIterator<String> iterator1 = list1.listIterator();
iterator1.add("D");
iterator1.add("E");
System.out.println(list1);//[D, E, a, b, c]

在集合的最后面按顺序添加若干元素

List<String> list2 = new ArrayList<>(Arrays.asList(new String[] { "a", "b", "c" }));
ListIterator<String> iterator2 = list2.listIterator();
while (iterator2.hasNext()) {
    iterator2.next();
}
iterator2.add("D");
iterator2.add("E");
System.out.println(list2);//[a, b, c, D, E]

在集合的每个元素的前面和后面都添加若干元素

List<String> list3 = new ArrayList<>(Arrays.asList(new String[] { "a", "b", "c" }));
ListIterator<String> iterator3 = list3.listIterator();
while (iterator3.hasNext()) {
    String item = list3.get(iterator3.nextIndex());
    iterator3.add(item + "前面1");
    iterator3.add(item + "前面2");
    iterator3.next();
    iterator3.add(item + "后面1");
    iterator3.add(item + "后面2");
}
System.out.println(list3);//[a前面1, a前面2, a, a后面1, a后面2, b前面1, b前面2, b, b后面1, b后面2, c前面1, c前面2, c, c后面1, c后面2]

在集合的指定元素的前面和后面添加若干元素

List<String> list4 = new ArrayList<>(Arrays.asList(new String[] { "a", "b", "c" }));
ListIterator<String> iterator4 = list4.listIterator();
while (iterator4.hasNext()) {
    String item = iterator4.next();
    if (item.equals("a") || item.equals("c")) {
        iterator4.previous();//先重新指向 item 的前面,这里不用担心 NoSuchElementException
        iterator4.add(item + "前面1");//在前面添加元素,添加后还是指向的 item 的前面
        iterator4.add(item + "前面2");//在前面添加元素,添加后还是指向的 item 的前面
        iterator4.next();//向后【再】移动一位,现在指向的是 item 的后面
        iterator4.add(item + "后面1");//在 item 的后面添加元素
        iterator4.add(item + "后面2");//在 item 的后面添加元素
    }
}
System.out.println(list4);//[a前面1, a前面2, a, a后面1, a后面2, b, c前面1, c前面2, c, c后面1, c后面2]

各种姿势测试 remove

可以随意 add,但 remove 必须要跟在 next() 或是 previous() 之后,而且只能执行一次。

替换指定元素

List<String> list1 = new ArrayList<String>(Arrays.asList(new String[] { "a", "b", "c", "b" }));
ListIterator<String> iterator1 = list1.listIterator();
while (iterator1.hasNext()) {
    if (iterator1.next().equals("b")) {
        iterator1.remove();
        iterator1.add("替换");
    }
}
System.out.println(list1);//[a, 替换, c, 替换]

移除某个元素及其前后的元素

List<String> list2 = new ArrayList<String>(Arrays.asList(new String[] { "a1", "a2", "a3", "b", "c1", "c2", "c3" }));
ListIterator<String> iterator2 = list2.listIterator();
while (iterator2.hasNext()) {
    String item = iterator2.next();
    if (item.equals("b")) {
        System.out.println("当前元素 " + item);
        iterator2.remove(); //移除当前元素
        if (iterator2.hasPrevious()) {
            System.out.println("前 1 个元素 " + iterator2.previous()); //移除此元素的前 1 个元素
            iterator2.remove();
        }
        if (iterator2.hasPrevious()) {
            System.out.println("前 2 个元素 " + iterator2.previous()); //移除此元素的前 2 个元素
            iterator2.remove();
        }
        if (iterator2.hasNext()) {
            System.out.println("后 1 个元素 " + iterator2.next()); //移除此元素的后 1 个元素
            iterator2.remove();
        }
        if (iterator2.hasNext()) {
            System.out.println("后 2 个元素 " + iterator2.next()); //移除此元素的后 2 个元素
            iterator2.remove();
        }
    }
}
System.out.println(list2);//[a1, c3]
//如果 item 为 a1 ,结果为 [b, c1, c2, c3]
//如果 item 为 a2 ,结果为 [c1, c2, c3]
//如果 item 为 c2 ,结果为 [a1, a2, a3]

注意:如果通过这种方式移除时,在遍历过程中如果发现了多个满足条件的 item,请一定要注意,在前一个 item 相关的 remove 操作结束后,整个集合已经改变,所以集合中的元素排列位置也已经改变,所以之前不在一起的元素可能就会因为中间的一些元素被删除掉而在一起了,这是你就要考虑到底是不是你需要的操作。

例如,同样是上面的案例,如果原始集合为

List<String> list2 = new ArrayList<String>(Arrays.asList(new String[] { "a1", "a2", "a3", "b", "c1", "c2", "c3", "b" }));

则结果不是你原以为的有一个元素 a1,而是一个空集合了,因为在 remove 最后一个 b 时,第一个元素 a1 也是他的第 "前 2 个元素" 。

移除指定范围内的所有元素

List<String> list3 = new ArrayList<String>(Arrays.asList(new String[] { "a", "开始", "b", "c", "d", "结束", "e" }));
ListIterator<String> iterator3 = list3.listIterator();
while (iterator3.hasNext()) {
    if (iterator3.next().equals("开始")) {
        iterator3.remove();//注释掉这行代码则不移除"开始"
        while (iterator3.hasNext()) {
            if (!iterator3.next().equals("结束")) {
                iterator3.remove();//remove之后必须再调用next方法后才能再remove
            } else {
                iterator3.remove();//注释掉这行代码则不移除"结束"
                break;//结束while循环
            }
        }
    }
}
System.out.println(list3);//[a, e]

2018-07-24

posted @ 2018-07-24 18:06  白乾涛  阅读(1369)  评论(0编辑  收藏  举报