Java源码分析 AbstractMap的keySet()方法

通过继承AbstractMap来实现一个Map

本章例子来自java编程思想小节——17.2.3 使用Abstract类——其中的Countries类。为了方便测试,将书中代码进行略微修改。该例子通过继承AbstractMap来实现了一个自己的Map,同时Map的数据放在了一个静态变量——体现了“享元设计模式”。
虽然本章例子和AbstractMapkeySet()方法没有直接关系,但体现了继承AbstractMap时只需要实现entrySet()方法即可,对于理解keySet()方法是很有帮助的。

package com;
import java.util.*;

public class Countries {
    public static final String[][] DATA = {
            // Africa
            {"ALGERIA","Algiers"}, {"ANGOLA","Luanda"},
            {"BENIN","Porto-Novo"}, {"BOTSWANA","Gaberone"},
            {"BURKINA FASO","Ouagadougou"},
    };
    // Use AbstractMap by implementing entrySet()
    private static class FlyweightMap extends AbstractMap<String,String> {
        private static class Entry implements Map.Entry<String,String> {
            int index;
            Entry(int index) { this.index = index; }
            public boolean equals(Object o) {
                return DATA[index][0].equals(o);
            }
            public String getKey() { return DATA[index][0]; }
            public String getValue() { return DATA[index][1]; }
            public String setValue(String value) {
                throw new UnsupportedOperationException();
            }
            public int hashCode() {
                return DATA[index][0].hashCode();
            }
        }
        // Use AbstractSet by implementing size() & iterator()
        static class EntrySet extends AbstractSet<Map.Entry<String,String>> {
            private int size;
            EntrySet(int size) {
                if(size < 0)
                    this.size = 0;
                    // Can't be any bigger than the array:
                else if(size > DATA.length)
                    this.size = DATA.length;
                else
                    this.size = size;
            }
            public int size() { return size; }
            private class Iter implements Iterator<Map.Entry<String,String>> {
                // Only one Entry object per Iterator:
                private Entry entry = new Entry(-1);
                public boolean hasNext() {
                    return entry.index < size - 1;
                }
                public Map.Entry<String,String> next() {
                    entry.index++;
                    return entry;
                }
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            }
            public Iterator<Map.Entry<String,String>> iterator() {
                return new Iter();
            }
        }
        private static Set<Map.Entry<String,String>> entries = new EntrySet(DATA.length);
        public Set<Map.Entry<String,String>> entrySet() {
            return entries;
        }
    }
    // Create a partial map of 'size' countries:
    static Map<String,String> select(final int size) {
        return new FlyweightMap() {
            public Set<Map.Entry<String,String>> entrySet() {
                return new EntrySet(size);
            }
        };
    }
    static Map<String,String> map = new FlyweightMap();
    public static Map<String,String> capitals() {
        return map; // The entire map
    }
    public static Map<String,String> capitals(int size) {
        return select(size); // A partial map
    }
    static List<String> names = new ArrayList<String>(map.keySet());
    // All the names:
    public static List<String> names() { return names; }
    // A partial list:
    public static List<String> names(int size) {
        return new ArrayList<String>(select(size).keySet());
    }
    public static void main(String[] args) {
        System.out.println(capitals(10));
        System.out.println(names(10));
        System.out.println(new HashMap<String,String>(capitals(3)));
        System.out.println(new LinkedHashMap<String,String>(capitals(3)));
        System.out.println(new TreeMap<String,String>(capitals(3)));
        System.out.println(new Hashtable<String,String>(capitals(3)));
        System.out.println(new HashSet<String>(names(6)));
        System.out.println(new LinkedHashSet<String>(names(6)));
        System.out.println(new TreeSet<String>(names(6)));
        System.out.println(new ArrayList<String>(names(6)));
        System.out.println(new LinkedList<String>(names(6)));
        System.out.println(capitals().get("BENIN"));
    }
} /* Output:
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo, BOTSWANA=Gaberone, BURKINA FASO=Ouagadougou}
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO]
{BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers}
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}
[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO]
Porto-Novo
*///:~
  • Countries类的静态变量DATA,作为实现的Map的真正数据存储。它作为一个二维数组,每个一维数组的长度都为2,0元素作为key,1元素作为value。
  • FlyweightMap就是本例实现的Map的名字,它作为一个静态内部类放在Countries类里面,且继承了抽象类extends AbstractMap<String,String>,而且你会发现实现一个Map很简单,只需要你实现entrySet()就够了。类似的,实现一个Set也很简单,只需要实现size() & iterator()就行,正如FlyweightMap里面的EntrySet静态内部类。
  • FlyweightMap为了实现entrySet()(其在Map.java接口里的签名为Set<Map.Entry<K, V>> entrySet();),在Countries类定义中定义了静态内部类EntrySet,其继承了AbstractSet<Map.Entry<String,String>>,刚好可以向上转型为Set<Map.Entry<K, V>>。但Map.Entry是一个接口,需要有具体的实现类,所以在Countries类定义中又定义了静态内部类Entry,其实现了Map.Entry<String,String>
  • EntrySet现在需要size() & iterator()。它继承了AbstractSet<Map.Entry<String,String>>。通过成员变量size和构造器,实现了size()iterator()的签名为Iterator<E> iterator();EntrySet中定义了一个成员内部类Iter实现了Iterator<Map.Entry<String,String>>,之所以是成员内部类,是因为set的迭代器必然应该持有外部类set的引用,换句话说,产生一个迭代器之前必然已经有了一个set对象。iterator()的方法实现逻辑也很合理,return new Iter();,这样每次都会新的迭代器对象,它们持有同一个外部类set对象的引用。

按照上面理清的思路,我们按照具体源码来分析:

        private static class Entry implements Map.Entry<String,String> {
            int index;
            Entry(int index) { this.index = index; }
            public boolean equals(Object o) {
                return DATA[index][0].equals(o);
            }
            public String getKey() { return DATA[index][0]; }
            public String getValue() { return DATA[index][1]; }
            public String setValue(String value) {
                throw new UnsupportedOperationException();
            }
            public int hashCode() {
                return DATA[index][0].hashCode();
            }
        }

Map.Entry接口需要实现的接口很多。虽然Entry类必须要表现出key和对应的value,但上面的实现来看,Entry类并没有真正存储key的数据和value的数据,它只是保存了一个index,进而能从静态变量DATA中得到数据,这就体现了享元设计模式。对于setValue方法,由于不希望key对应的value被改变,所以这里没有实现,直接抛出UnsupportedOperationException

            private class Iter implements Iterator<Map.Entry<String,String>> {
                // Only one Entry object per Iterator:
                private Entry entry = new Entry(-1);
                public boolean hasNext() {
                    return entry.index < size - 1;
                }
                public Map.Entry<String,String> next() {
                    entry.index++;
                    return entry;
                }
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            }
            public Iterator<Map.Entry<String,String>> iterator() {
                return new Iter();
            }

有了之前实现好的Entry类,这里就可以直接使用了。Iter类拥有一个Entry对象,作为迭代器访问数据的视图,而且Entry对象的index成员总是会被初始化为-1,这是合理的,观察hasNext()的逻辑,访问了外部类对象的size成员(因为Iter类是成员内部类才可以),当外部类set的长度size为0时,size - 1为-1,这样就算是初始的迭代器的hasNext()也会返回false。当外部类set的长度size为1时,size - 1为0,这样迭代器第一次判断hasNext()时能返回true,但迭代器next()后,就只能返回false了。

next()的实现逻辑也很简单,只需要让entry.index++就可以了,这样就能代表迭代器往后移动了。

定义好Iter类也是为了实现iterator()方法。

        static class EntrySet extends AbstractSet<Map.Entry<String,String>> {
			//省略
        }
        private static Set<Map.Entry<String,String>> entries = new EntrySet(DATA.length);
        public Set<Map.Entry<String,String>> entrySet() {
            return entries;
        }

定义好EntrySet类也是为了实现entrySet()方法,只是这里通过返回一个静态变量来实现。

    // Create a partial map of 'size' countries:
    static Map<String,String> select(final int size) {
        return new FlyweightMap() {
            public Set<Map.Entry<String,String>> entrySet() {
                return new EntrySet(size);//这里不需写成FlyweightMap.EntrySet()
            }
        };
    }

select方法可以返回一个只有部分数据的Map。创建了FlyweightMap的匿名内部类,并且override了entrySet()方法,通过形参size传递给EntrySet的构造器,达到目的。注意调用EntrySet的构造器,不需要写成FlyweightMap.EntrySet(),因为匿名内部类本质就是继承,而继承后EntrySet作为FlyweightMap的静态内部类,当然也是属于FlyweightMap的静态成员了,所以这里就直接访问了父类FlyweightMap的静态成员了(这里是直接访问了静态成员的构造器)。

public class test {
    static class A {
        void doSomething() {}
        static class B {}
    }

    static void select() {
        //new B();//编译错误
        new A.B();
    }

    static void select1() {
        new A() {
            @Override
            void doSomething() {
                new B();//继承了A,就可以直接使用父类A的静态成员B了,只不过B是一个静态内部类
            }
        };
    }
}

上面这个例子可以解释“调用EntrySet的构造器,不需要写成FlyweightMap.EntrySet()”的原因。

	System.out.println(new HashMap<String,String>(capitals(3)));

capitals方法也会返回一个FlyweightMap对象,但这里会调用到HashMap的构造器:

//HashMap.java
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);//这里会把传入的map的键值对放入新构造的HashMap里
            }
        }
    }

分析AbstractMap的keySet()方法

前面已经讲过,FlyweightMap继承AbstractMap<String,String>后只需要实现entrySet()方法,同样,EntrySet继承AbstractSet<Map.Entry<String,String>>后只需要实现size() & iterator()方法。之所以我们需要实现的地方这么少,是因为AbstractXXX抽象类已经帮我们实现了许多方法,比如AbstractMap抽象类里面就已经实现了Map接口里的很多方法,同样,AbstractSet抽象类里面也已经实现了Set接口里的很多方法。

    static List<String> names = new ArrayList<String>(map.keySet());

很明显,本文的代码并没有实现keySet()方法,实际上它的实现已经在AbstractMap里写好了:

//AbstractMap.java file
//省略
    transient Set<K>        keySet;
    transient Collection<V> values;
    
    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new AbstractSet<K>() {
                public Iterator<K> iterator() {
                    return new Iterator<K>() {
                        private Iterator<Entry<K,V>> i = entrySet().iterator();

                        public boolean hasNext() {
                            return i.hasNext();
                        }

                        public K next() {
                            return i.next().getKey();
                        }

                        public void remove() {
                            i.remove();
                        }
                    };
                }

                public int size() {
                    return AbstractMap.this.size();
                }

                public boolean isEmpty() {
                    return AbstractMap.this.isEmpty();
                }

                public void clear() {
                    AbstractMap.this.clear();
                }

                public boolean contains(Object k) {
                    return AbstractMap.this.containsKey(k);
                }
            };
            keySet = ks;
        }
        return ks;
    }
//省略    
  • 首先这里设计成了一种单例模式,keySet()方法返回是一个AbstractSet的匿名内部类,这个匿名内部类是持有着外部类map对象的引用的,这样保证了一个map只对应一个key的set。

  • AbstractSet的匿名内部类需要重写实现iterator()方法,方法内部则是返回一个Iterator的匿名内部类,它也是持有了外部类对象的引用的,所以这里,它直接调用了entrySet()方法,这是它的外部类对象(AbstractSet的匿名内部类)的外部类对象(外部类map对象)的方法。

  • 观察Iterator的匿名内部类的实现,它有一个成员也是迭代器,而且是entrySet()的迭代器,有了entrySet()的迭代器后,它自己的方法实现都全靠自己的迭代器成员了。有点像装饰器模式,因为成员本来是entry的迭代器,但包装以后变成了key的迭代器。

  • 除了iterator()方法外,size()方法也是必须要实现的。return AbstractMap.this.size();,原来是调用持有的外部类map对象的size()方法,它的实现就在AbstractMap的源码里:public int size() { return entrySet().size(); }。好家伙,和iterator()方法一样,原来size()方法的实现也是依靠了entrySet()方法的返回值的。

  • 从上面分析看来,size() & iterator()方法的重写实现,都是依靠了外部类对象的entrySet()方法返回的entryset,但entrySet()方法在AbstractMap里面并没有实现,它需要被AbstractMap的使用者来实现。

  • 之前说过继承AbstractSet<Map.Entry<String,String>>后只需要实现size() & iterator()方法,但源码里还有另外三个方法也重写实现了。这三个方法本来不是必须实现的,因为AbstractCollection里面已经有实现。

  • isEmpty()方法重写实现了,实现逻辑是return AbstractMap.this.isEmpty(),调用的外部类对象的方法,从AbstractMap源码里找到该方法的实现:public boolean isEmpty() { return size() == 0; },此时调用的层次已经到了AbstractMap,从AbstractMap源码里找到size()方法的实现:public int size() { return entrySet().size(); }。所以这里也是依靠了entrySet()方法的返回值的。

  • 这里如果不重写isEmpty()方法,由于AbstractSet继承了AbstractCollection,那么isEmpty()方法的实现就在继承的AbstractCollection里面:public boolean isEmpty() { return size() == 0; },这样的话调用的是AbstractSet重写的size()方法,不过最终都会调用到AbstractMap子类实现的entrySet()方法。重不重写isEmpty()方法这两种情况的调用过程如下:
    在这里插入图片描述

  • 从上图可以看出,重不重写isEmpty()方法最终调用的方法都是AbstractMap子类实现的entrySet()方法,绿色虚线下的调用过程两种情况都是一样的了,但重写了isEmpty()方法后,能让调用链更早地调用到外部类的实现上(注意红色字)。我觉得之所以要重写,是因为keyset的设计逻辑只是作为entryset的一种视图,最终实现还是得依赖于entryset,既然最终实现都是来自于AbstractMap子类实现的entrySet()方法,那么更早地调用到外部类的方法上也是更合理的。

  • clear()方法也重写实现了,实现逻辑是AbstractMap.this.clear(),调用的外部类对象的方法,从AbstractMap源码里找到该方法的实现:public void clear() { entrySet().clear(); },原来是直接调用entrySet()的clear方法。这里如果不重写clear()方法,由于AbstractSet继承了AbstractCollection,那么clear()方法的实现就在继承的AbstractCollection里面:

    public void clear() {
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
    }
  • 那么直接调用entrySet()的clear方法到底是什么样的实现呢,从本文例子代码里可以看到静态内部类EntrySet继承AbstractSet时只重写了size() & iterator()方法,所以其实entrySet()的clear方法就是AbstractCollection里面clear方法。
  • clear()方法重写和不重写这两种情况,最终调用的clear都是entryset的clear(1.重写时,直接用entry了,public void clear() { entrySet().clear(); };2.不重写时,第一句Iterator<E> it = iterator(),由于iterator()已经重写,那么iterator()会返回keyset的迭代器,然而keyset的迭代器是通过包装entryset的迭代器来的,所以实际也是调用entryset的clear)。
    在这里插入图片描述
  • contains()方法也重写实现了,实现逻辑是return AbstractMap.this.containsKey(k),调用的外部类对象的方法,从AbstractMap源码里找到该方法的实现。其内部实现主要依靠了entrySet()的迭代器。
    public boolean containsKey(Object key) {
        Iterator<Map.Entry<K,V>> i = entrySet().iterator();
        if (key==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    return true;
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return true;
            }
        }
        return false;
    }
  • 这里如果不重写contains()方法,由于AbstractSet继承了AbstractCollection,那么contains()方法的实现就在继承的AbstractCollection里面。里面调用了iterator(),由于iterator()之前已经重写过了(返回针对于key的迭代器,区别只是key的迭代器在取值时多执行了句getKey()),那么不重写contains()方法的情况调用contains()方法也能返回正确的结果。
    public boolean contains(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return true;
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return true;
        }
        return false;
    }

分析总结

  • 从上面分析可以看出,keySet()方法里面的AbstractSet匿名内部类,除了必须实现的size() & iterator()方法,其实另外三个方法不需要重写也能得到同样的效果。
  • 但keySet只是被设计成EntrySet的一种视图,keySet的方法实现都依赖于对EntrySet的调用。所以对另外三个方法isEmpty() & clear() & contains()的重写可以让“keySet只是EntrySet的一种视图”这个逻辑更加清晰,让这三个方法也直接依赖于EntrySet。
  • 但不重写也能达到相同效果是建立在AbstractMap的实现逻辑和AbstractCollection实现逻辑一样的条件上的(因为不重写就会调用到继承于AbstractCollection的实现),如果实现逻辑都不一样,而且因为keySet的方法实现都应该依赖于外部类map对象的实现,所以这里必须重写isEmpty() & clear() & contains(),且重写后的逻辑必须是直接调用到外部类map对象的实现
  • keySet()方法的逻辑和成员transient Set<K> keySet;配合使用,使得keySet这个set对象成为一种单例模式
  • 重写的iterator()方法里面的Iterator的匿名内部类,用到了装饰器模式,这个Iterator的匿名内部类继承了Iterator的同时,还拥有一个Iterator类型的成员,而且所有方法实现都依赖于这个成员。
发布了171 篇原创文章 · 获赞 130 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/anlian523/article/details/102990531