Java 集合深入理解(5):AbstractCollection

今天好累,来学学 AbstractCollection 吧!

什么是 AbstractCollection

AbstractCollection 是 Java 集合框架中 Collection 接口 的一个直接实现类, Collection 下的大多数子类都继承 AbstractCollection ,比如 List 的实现类, Set的实现类。

它实现了一些方法,也定义了几个抽象方法留给子类实现,因此它是一个抽象类

抽象方法

  1. public abstract Iterator<E> iterator();
  2. public abstract int size();

子类必须以自己的方式实现这两个方法。除此外,AbstractCollection 中默认不支持添加单个元素,如果直接调用 add(E) 方法,会报错:

public boolean add(E object) {
    throw new UnsupportedOperationException();
}

因此,如果子类是可添加的数据结构,需要自己实现 add(E) 方法。

实现的方法

1.addAll() 添加一个集合内的全部元素:

public boolean addAll(Collection<? extends E> collection) {
    boolean result = false;
    //获取待添加对象的迭代器
    Iterator<? extends E> it = collection.iterator();
    while (it.hasNext()) {
        //挨个遍历,调用 add() 方法添加,因此如果没有实现 add(E) 方法,addAll() 也不能用
        if (add(it.next())) {
            result = true;
        }
    }
    return result;
}

2.clear() 删除所有元素:

public void clear() {
    //获取子类实现的迭代器,挨个遍历,删除
    Iterator<E> it = iterator();
    while (it.hasNext()) {
        it.next();
        //单线程使用迭代器的 remove() 方法不会导致 fail-fast
        it.remove();
    }
}

3.contains() 是否包含某个元素:

public boolean contains(Object object) {
    //获取子类实现的迭代器,挨个遍历,比较
    Iterator<E> it = iterator();
    if (object != null) {
        while (it.hasNext()) {
            //这个元素的类 需要重写 equals() 方法,不然结果够呛
            if (object.equals(it.next())) {
                return true;
            }
        }
    } else {
        //目标元素是空也能查找,说明 AbstractCollection 默认是支持元素为 null 的
        while (it.hasNext()) {
            if (it.next() == null) {
                return true;
            }
        }
    }
    return false;
}

4.containsAll() 是否包含指定集合中的全部元素:

public boolean containsAll(Collection<?> collection) {
    Iterator<?> it = collection.iterator();
    //挨个遍历指定集合
    while (it.hasNext()) {
        //contails 里也是遍历,双重循环,O(n^2)
        if (!contains(it.next())) {
            return false;
        }
    }
    return true;
}

5.isEmpty() 是否为空:

public boolean isEmpty() {
    //调用子类实现的 size() 方法
    return size() == 0;
}

6.remove() 删除某个元素:

public boolean remove(Object object) {
    //获取子类实现的 迭代器
    Iterator<?> it = iterator();
    if (object != null) {
        while (it.hasNext()) {
            if (object.equals(it.next())) {
                it.remove();
                return true;
            }
        }
    } else {
        while (it.hasNext()) {
            if (it.next() == null) {
                it.remove();
                return true;
            }
        }
    }
    return false;
}

受不了了,又是 if-else,直接写成这样看着不是更舒服吗:

public boolean remove(Object object) {
    Iterator<?> it = iterator();
    while (it.hasNext()) {
       if (object != null ? object.equals(it.next()) : it.next() == null) {
           it.remove();
           return true;
        }
    }
    return false;
}

7.removeAll() 删除指定集合中包含在本集合的元素:

public boolean removeAll(Collection<?> collection) {
    boolean result = false;
    Iterator<?> it = iterator();
    while (it.hasNext()) {
        //双重循环
        if (collection.contains(it.next())) {
            it.remove();
            result = true;
        }
    }
    return result;
}

8.retainAll() 保留共有的,删除指定集合中不共有的:

public boolean retainAll(Collection<?> collection) {
    boolean result = false;
    Iterator<?> it = iterator();
    while (it.hasNext()) {
        //排除异己,不在我集合中的统统 886
        if (!collection.contains(it.next())) {
            it.remove();
            result = true;
        }
    }
    return result;
}

9.toArray(), toArray(T[] contents) 转换成数组:

public Object[] toArray() {
//把集合转换成 ArrayList,然后再调用 ArrayList.toArray() 
    return toArrayList().toArray();
}

public <T> T[] toArray(T[] contents) {
    return toArrayList().toArray(contents);
}

@SuppressWarnings("unchecked")
private ArrayList<Object> toArrayList() {
    ArrayList<Object> result = new ArrayList<Object>(size());
    for (E entry : this) {
        result.add(entry);
    }
    return result;
}

ArrayList, 集合与数组的桥梁。

10.toString() 把内容转换成一个 String 进行展示:

public String toString() {
    if (isEmpty()) {
        return "[]";
    }
    //注意默认容量是 size() 的 16 倍,为什么是 16 呢?
    StringBuilder buffer = new StringBuilder(size() * 16);
    buffer.append('[');
    //仍旧用到了迭代器
    Iterator<?> it = iterator();
    while (it.hasNext()) {
        Object next = it.next();
        if (next != this) {
            //这个 Object 也得重写 toString() 方法,不然不能输出内容
            buffer.append(next);
        } else {
            buffer.append("(this Collection)");
        }
        if (it.hasNext()) {
            buffer.append(", ");
        }
    }
    buffer.append(']');
    return buffer.toString();
}

我们之所以可以使用 System.out.print() 直接输出集合的全部内容,而不用挨个遍历输出,全都是 AbstractCollection 的功劳!

    List list = new LinkedList();
    System.out.println(list);

其他

1.AbstractCollection 默认的构造函数是 protected:

/**
 * Sole constructor.  (For invocation by subclass constructors, typically
 * implicit.)
 */
protected AbstractCollection() {
}

因此,官方推荐子类自己创建一个 无参构造函数:

The programmer should generally provide a void (no argument) and Collection constructor, as per the recommendation in the Collection interface specification.

2.AbstractCollection 的 add(E) 方法默认是抛出异常,这样会不会容易导致问题?为什么不定义为抽象方法?

答案译自 stackoverflow :

  • 如果你想修改一个不可变的集合时,抛出 UnsupportedOperationException 是标准的行为,比如 当你用 Collections.unmodifiableXXX() 方法对某个集合进行处理后,再调用这个集合的 修改方法(add,remove,set…),都会报这个错;
  • 因此 AbstractCollection.add(E) 抛出这个错误是准从标准;

那为什么会有这个标准呢?

在 Java 集合总,很多方法都提供了有用的默认行为,比如:

  • Iterator.remove()
  • AbstractList.add(int, E)
  • AbstractList.set(int, E)
  • AbstractList.remove(int)
  • AbstractMap.put(K, V)
  • AbstractMap.SimpleImmutableEntry.setValue(V)

而之所以没有定义为 抽象方法,是因为可能有很多地方用不到这个方法,用不到还必须实现,这岂不是让人很困惑么。

个人觉得原因跟和设计模式中的 接口隔离原则 有些相似:

不要给客户端暴露不需要的方法。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小灰灰

Java反射的使用姿势一览

反射的学习使用 日常的学习工作中,可能用到反射的地方不太多,但看看一些优秀框架的源码,会发现基本上都离不开反射的使用;因此本篇博文将专注下如何使用 本片博文布...

1956
来自专栏小白鼠

Java8特性接口的改变LambaStream时间API

函数式接口,该接口中只能由一个抽象方法,可以使用@FunctionalInterface注解修饰某个接口有且仅有一个抽象方法。

722
来自专栏Java技术栈

字符串拼接+和concat的区别

+和concat都可以用来拼接字符串,但在使用上有什么区别呢,先来看看这个例子。 public static void main(String[] args) ...

2514
来自专栏林德熙的博客

C# 枚举转字符串 枚举转字符串字符串转枚举

如果把一个枚举转字符串,那么如何把字符串转枚举?可以使用 Enum.Parse 不过这个方法可以会抛异常,所以使用需要知道字符串是可以转

551
来自专栏Java技术分享

java5的静态导入和自动装箱拆箱

静态导入:导入某个类的静态成员(属性和方法) 语法:import static 可以简化编程,但是可读性较差,所以使用较少,仅作为了解。 自动装箱:将原始...

1975
来自专栏维C果糖

详述 Java 语言中的 String、StringBuffer 和 StringBuilder 的使用方法及区别

1 简介 在 Java 语言中,共有 8 个基本的数据类型,分别为:byte、short、int、long、float、double、boolean和char,...

1925
来自专栏nnngu

014 Java的反射机制

这篇文章要总结java的反射机制,将从以下几点进行总结: 一、什么是反射机制 二、哪里用到反射机制 三、反射机制的优点与缺点 四、利用反射机制能获得什么信息 五...

2423
来自专栏Linyb极客之路

通过javap命令分析java汇编指令

javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常...

462
来自专栏静默虚空的博客

[Java IO]04_系统标准IO

System表示系统类,它有3个与 Java IO 有关的常量。 System.out——系统标准输出 System.in——系统标准输入 System....

17410
来自专栏行者常至

(15)Struts2_OGNL读取Object栈

541

扫码关注云+社区