40个Java集合面试问题和答案

转自importNew :http://www.importnew.com/15980.html

这篇个人blog上也有相应分析:http://www.mingzhe.org/?tag=java-2


4.为何Collection不从Cloneable和Serializable接口继承?

Collection接口指定一组对象,对象即为它的元素。如何维护这些元素由Collection的具体实现决定。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现。当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制。特定的实现应该决定它是否可以被克隆和序列化

个人理解:Collection extends 哪些接口就表示要具有哪些特征,extends的越多,限制要求就越多,而Collection只是extends了Iterable,表示Collection只需要具有可迭代的特性就可以了(Iterable接口中只定义了三个方法:hasNext(), next(), remove())

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

public abstract classAbstractList<E> extendsAbstractCollection<E> implementsList<E>

public interfaceList<E> extendsCollection<E>

public interface Collection<E> extends Iterable<E> 

5.为何Map接口不继承Collection接口?

尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

 transient Set<Map.Entry<K,V>>entrySet;

    public Set<K>keySet() { //抽取key的方法,返回Set(因为key不允许重复)
        Set<K> ks;
        return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
    }

    public Collection<V>values() { //抽取value的方法,返回Collection (因为value允许重复)
        Collection<V> vs;
        return (vs = values) == null ? (values = new Values()) : vs;
    }

    public Set<Map.Entry<K,V>>entrySet() { //抽取key/value的方法,返回Set
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

public abstract class AbstractMap<K,V> implements Map<K,V> 

public interface Map<K,V>

PS: 由entrySet()得到Set<Map.Entry<K,V>>,可通过 迭代器 访问其中每一个元素,在Map接口中有定义Entry接口,Entry接口中又定义了getKey(),getValue(),setValue()等方法。

15.为何Iterator接口没有具体的实现?

Iterator 接口只是用于声明迭代集合的方法,但它的实现是依赖于具体的类的。 每个集合类都通过嵌套类的方式实现了自己的Iterator接口从而返回用于遍历的iterator对象。这样可以让集合类自己选择迭代器的实现是 fail-fast(每迭代一次时都检测集合结构是否发生变化,若变化则抛出CME异常)还是fail-safe(并发执行,不会抛出CME异常)机制。例如,ArrayList的迭代器就是fail-fast,而CopyOnWriteArrayList 的迭代器则是fail-safe的。

以ArrayList类源码中的iterator()方法和Itr内部类为例来分析:

public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

15.HashMap的工作原理?

HashMap将键值对存储在实现 了Map.Entry接口的静态嵌套类中。HashMap是以哈希算法为基础的,在放入或取回元素时都会调用hashCode() 和equals()方法。当调用put()方法时,HashMap会调用“键”的hashCode()方法计算出”键值对”(Entry)将要存储在数组 中的位置(索引)。而Entry又是存储在LinkedList中,因此当计算出的位置已经有entry占用时,再调用equals()方法检查key是 否相同,如果相同那么用新值覆盖旧值,如果不相同,那么创建一个新的Entry,将键值对存入其中,再将Entry添加到LinkedList中。当调用 get()方法时,又会调用hashCode()方法来找出元素在数组中的位置,然后再调用equals()方法依次检查key是否相同,相同则返回 value。

对于HashMap,还要着重了解的是初始容量(capacity)、加载因子(load factor)、自动扩容(threshold resizing)机制。HashMap默认初始容量是32,加载因子是0.75。阈值(threshold)=初始容量x加载因子,当map的大小超过 阈值时,则要对该哈希表进行rehash操作,即重建内部数据结构(这一过程可能会引起性能问题),从而哈希表将具有大约两倍的容量,容量通常是2的幂次方。因此,如果能估测出元素的数量,那么最好在初始化HashMap时指定合适的capacity和 load factor。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值