zoukankan      html  css  js  c++  java
  • Java 核心技术-集合-集合框架

    说在前面的话:

    关于Core Java 集合方面的博文网上已经写烂了,为啥我还要写呢? 

    答:他们写的都很好,我也学到不少东西,如果把我当做一个系统的话,学习别人、看书、读源码是输入,但是往往形不成一个回路,形不成回路就会与行业脱节,所以我也要输出一些东西,尽管这些东西大家耳熟能详。

    本文适合的读者?

    答:会简单用Java 集合类库,看过core java volume 1但是不知其所以然的同学。

    废话不多说,大家找你们感兴趣的点吧,也可以多多提提建议。



    没有目录的博文都是耍流氓。

    集合框架

    视图与包装器

    视图技术的应用

      轻量级集包装器

      子范围

      不可修改的视图

      同步视图

      检查视图

      关于可选操作的说明

    批处理

    数组与集合之间的转换

    集合框架

    框架是一个类的集,它奠定了创建高级功能的基础。框架包含很多超类,这些超类拥有非常有用的功能、策略和机制。框架使用者创建的子类可以扩展超类的功能,而不必重新创建这些有用的机制。

    Java集合类库中有很多有用的接口、抽象类和具体类,这些对使用者来说非常有帮助,你需要用的时候直接拿来用就可以了,需要扩展功能的实现相应的接口和扩展类即可。

    集合类库的接口关系图这里就不画了,基本的接口有Collection和Map。还有Iterator接口。这些接口被一些更具体的接口继承,来提供某种具体集合的服务。除此之外,还有一个标记接口RandomAccess,这个接口没有任何方法,只是用来检测一个特定的集合是否支持高效的随机访问。

    视图与包装器

    使用视图可以获得其他的实现了集合接口和映射表接口的对象。

    例如:映射表类的keySet方法,看起来好像它创建了一个新集(Set),并将映射表中的所有键都填进去,然后返回这个集。事实是该方法返回一个实现Set接口的类对象,这个类方法对原映射表进行操作。这种集合就称为视图。

    视图技术的应用

    轻量级集包装器

    Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器,该方法可以将数组传递给一个希望得到列表或者集合变量的方法。

    1 String[] values = new String[10];
    2 List<String> aList = Arrays.asList(values);

    返回的对象不是ArrayList,而是一个视图对象,带有访问底层数组的get和set方法。改变数组大小的所有方法都会抛出一个异常(不支持的操作)。

    源码解析:

    // Arrays asList方法源码
    @SafeVarargs
    public static <T> List<T> asList(T... a) {
    // 返回构造的ArrayListd对象,注意构造方法的参数,并不是java.util.ArrayList支持的参数。
            return new ArrayList<>(a); 
    }

    这里使用的ArrayList类是Arrays的内部类

    private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable
        {
            private static final long serialVersionUID = -2764017481108945198L;
            private final E[] a;
            ArrayList(E[] array) { // 构造函数接收数组作为参数
                if (array==null)
                    throw new NullPointerException();
                a = array;
            }
            public Object[] toArray() {  //默认的toArray方法返回Object[]
                return a.clone();
            }
            public <T> T[] toArray(T[] a) {  //返回传入参数类型的数组
                int size = size();
                if (a.length < size)
                    return Arrays.copyOf(this.a, size,
                                         (Class<? extends T[]>) a.getClass());
                System.arraycopy(this.a, 0, a, 0, size);
                if (a.length > size)
                    a[size] = null;
                return a;
            }
         // 没有覆盖add remove等改变数组大小的方法
         // get set indexof contains 方法省略……
         }

    为什么改变数组的大小的方法会抛出异常呢? ArrayList<E> 继承自AbstractList类,AbstractList类中定义的改变数组大小的方法默认抛出异常。

    public void add(int index, E element) {
            throw new UnsupportedOperationException();
    }
    public E remove(int index) {
            throw new UnsupportedOperationException();
    }

    下一个操作,Collections.nCopies();

    List<anObject> oList = Collections.nCopies(n,anObject);

    将返回一个实现了List接口的不可修改的对象(不能修改引用,并没说对象本身不可以修改)。

    该方法和Arrays.asList()方法类似,也是返回一个内部类(CopiesList)的对象,该内部类继承AbstractList类,没有覆盖修改对象的方法(所以不支持修改该对象)

    public static <T> List<T> nCopies(int n, T o) {
            if (n < 0)
                throw new IllegalArgumentException("List length = " + n);
            return new CopiesList<>(n, o);
    }
    private static class CopiesList<E>
            extends AbstractList<E>
            implements RandomAccess, Serializable
        {
            private static final long serialVersionUID = 2739099268398711800L;
            final int n;
            final E element;  // 只有这一个元素,对外看起来就像是有n个元素一样
            CopiesList(int n, E e) {
                assert n >= 0;
                this.n = n;
                element = e;
            }
            public boolean contains(Object obj) {
                return n != 0 && eq(obj, element);
            }
            public int indexOf(Object o) {
                return contains(o) ? 0 : -1;
            }
            public int lastIndexOf(Object o) {
                return contains(o) ? n - 1 : -1;
            }
            public E get(int index) {
                if (index < 0 || index >= n)
                    throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+n);
                return element;
            }
            public Object[] toArray() {
                final Object[] a = new Object[n];
                if (element != null)
                    Arrays.fill(a, 0, n, element);
                return a;
            }
            public <T> T[] toArray(T[] a) {
                final int n = this.n;
                if (a.length < n) {
                    a = (T[])java.lang.reflect.Array
                        .newInstance(a.getClass().getComponentType(), n);
                    if (element != null)
                        Arrays.fill(a, 0, n, element);
                } else {
                    Arrays.fill(a, 0, n, element);
                    if (a.length > n)
                        a[n] = null;
                }
                return a;
            }
            public List<E> subList(int fromIndex, int toIndex) {
                if (fromIndex < 0)
                    throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
                if (toIndex > n)
                    throw new IndexOutOfBoundsException("toIndex = " + toIndex);
                if (fromIndex > toIndex)
                    throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
                return new CopiesList<>(toIndex - fromIndex, element);
            }
    }

    同理 Collections 类中还有很多方法都是按照类似的节奏来对外提供服务的,Collections.singleton(anObject); 返回一个视图对象,该对象实现了Set<E>接口,其实是继承了AbstractSet抽象类。返回的对象实现了一个不可修改的单元素集。

    singletonList方法和singletonMap方法类似。具体请参见Collections源代码。

    (源码面前,了无秘密)

    子范围

    很多集合都支持建立子范围(subrange)视图。对于列表,可以使用subList方法来获得一个列表的子范围视图。

    List<String> group = aList.subList(10,20);

    可以将任何操作应用于子范围(包括改变列表大小,也可以清空),并且能够自动反映整个列表的情况(因为子范围返回的对象是列表内部类的对象,也就是列表的视图,它们共用一份数组。)

    对于有序集和映射表,可以使用排序顺序而不是元素位置建立子范围。

    SortedSet<E>  subSet(E from,E to)
    SortedSet<E>  headSet(E to)
    SortedSet<E>  tailSet(E from)
    SortedMap<K,V>  subMap(K from , K to)
    SortedMap<K,V>  headMap(K to)
    SortedMap<K,V>  tailMap(K from)

    以上方法返回有序集和映射表的视图。

    Java SE 6引入NavigableSet接口赋予子范围操作更多的控制能力。可以指定是否包含边界:

    NavigableSet<E>  subSet(E from,boolean fromInclusive, E to, boolean toInclusive)
    NavigableSet<E>  headSet(E to,boolean toInclusive)
    NavigableSet<E>  tailSet(E from,boolean fromInclusive)

    不可修改视图

    Collections 还有几个方法用于产生集合的不可修改视图,这些视图对现有集合增加一个运行时的检查。如果发现试图对集合进行修改,就抛出一个异常,同时这个集合将保持未修改的状态。

    这些方法有:

    Collections.unmodifiableCollection
    Collections.unmodifiableList
    Collections.unmodifiableSet
    Collections.unmodifiableSortedSet
    Collections.unmodifiableMap
    Collections.unmodifiableSortedMap

    不可修改视图并不是集合本身不可修改。仍然可以通过集合的原始引用进行修改。

    同步视图

    由于普通集合类不能保证被多线程访问的安全性。

    类库的设计者提供了一种视图机制保证常规集合的线程安全,而不是实现线程安全的集合类。例如: Collections类的静态synchronizedMap方法可以将任何一个映射表转换成具有同步访问方法的Map。

    Map<String,Employee> map =
    Collections.synchronizedMap(new HashMap<String,Employee>());

    通过源码我们可以了解到,集合框架Collections 给我们提供了很多有用的内部类,这些内部类担当常规集合的同步视图,个人感觉有点像装饰者模式。为了让大家有一个直观的认识,这些同步内部类的继承结构如下图所示(原谅我作图实在是。):

    其实同步视图的原理是在访问常规集合的时候加了个锁,具体可以参见源码。很清晰,也很简单。

    检查视图

    Java SE 5 增加了一组“检查”视图,用来对泛型类型发生问题时提供调试支持。

    错误举例:

    ArrayList<String> strings = new ArrayList<>();
    ArrayList rawList = strings;
    rawList.add(new Date());   // add不会出错,另一部分代码调用get方法,并转换为String 就会抛出异常了。

    检查视图可以探测到这类问题。

    List<String>  safeStrings = Collections.checkedList(strings,Sring.class);

    原理其实很简单,在调用集合add方法之前检验下添加元素的类型和集合元素的类型是否匹配。

    源码如下:

            void typeCheck(Object o) {
                if (o != null && !type.isInstance(o))
                    throw new ClassCastException(badElementMsg(o));
            }
            private String badElementMsg(Object o) {
                return "Attempt to insert " + o.getClass() +
                    " element into collection with element type " + type;
            }
            CheckedCollection(Collection<E> c, Class<E> type) {
                if (c==null || type == null)
                    throw new NullPointerException();
                this.c = c;
                this.type = type;
            }

    关于可选操作的说明

    通常,视图有一些局限性,可能只可以读、无法改变大小、只支持删除而不支持插入,这些与映射表的键视图情况相同。如果试图进行恰当的操作,受限制视图就会抛出 UnsupportedOperationException。

    这样很好的限制了接口数量的成倍增加。

    批操作

    使用类库中的批操作避免频繁的使用迭代器

    集合批操作

    批操作含义

    retainAll(Collection<?> c):boolean

    保留参数中不存在的元素

    removeAll(Collection<?> c):boolean

    删除参数中存在的元素

    addAll(Collection<?> c):boolean

    添加参数中存在的元素

    clear():void

    清空集合

    集合与数组之间的转换

    由于集合框架比Java平台的大部分API生的晚,所以有时候为了兼容,需要在传统的数组和现代的集合之间进行转换。

    数组转换为集合,Arrays.asList的包装器可以实现:

    String [] values = ...;
    HashSet<String> staff = new HashSet<String>(Arrays,asList(values));

    将集合转换为数组:

    (1)   使用toArray方法

    Object[] values = staff.toArray();

    这样做的结果是产生一个对象数组。即使知道集合中包含一个特定类型的对象,也不能使用类型转换。

    String[] values = (String[]) staff.toArray();  //error

    toArray方法返回的数组是一个Object[]数组,无法改变其类型。

    (2)   使用另外一种toArray方法,并将其设计为所希望的元素类型且长度为0的数组,随后所返回的数组将和所创建的数组一样:

    String[] values = staff.toArray(new String[0]); // right
  • 相关阅读:
    扫面线模板
    (动态规划、栈)leetcode 84. Largest Rectangle in Histogram, 85. Maximal Rectangle
    tmux 常见命令汇总
    leetcode 221
    leetcode 319 29
    (贪心)leetcode 392. Is Subsequence, 771. Jewels and Stones, 463. Island Perimeter
    leetcode 982 668
    Python import 同文件夹下的py文件的函数,pycharm报错
    Windows里Anaconda-Navigator无法打开的解决方案
    Windows下 gpu版 Tensorflow 安装
  • 原文地址:https://www.cnblogs.com/googny/p/4121049.html
Copyright © 2011-2022 走看看