zoukankan      html  css  js  c++  java
  • 持有对象-4

    Set

    Set不保存重复的元素(至于如何判断元素相同则较为复杂,稍后便会看到)。如果你试图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象。Set中 最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因如此,查找就成为了Set中最重要的操作,因此你通常都会选择一个HashSet的实现, 它专门对快速查找进行了优化。
          Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。) Set是基于对象的值来确定归属性的,而更加复杂的问题我们将在以后中介绍。
    下面是存放Integer对象的HashSet实例
    Set<Integer> d=new HashSet<Integer>();
            
            d.add(12);
            d.add(13);
            d.add(14);
            d.add(15);
            d.add(16);
            d.add(17);
            
            System.out.println(d);

    你可以看到结果输出没有顺序,这是出于速度的考虑,HashSet使用了散列。HashSet所维护的顺序与TreeSet或者LinkedHashSet都不同,因为他们具有不用的元素实现方式。TreeSet将数据存储在红黑树数据结构中,而HashSet使用的是散列函数。LinkedHashSet因为查询速度的原因也使用了散列函数

    ,但是它看起来使用链表来维护元素的插入顺序。

    如果你想对结果排序,一种方式是使用TreeSet代替

    Random rand = new Random(47);
            Set<Integer> s = new TreeSet<Integer>();
            
            for (int i = 0; i < 200; i++) {
                s.add(rand.nextInt(30));
            }
            System.out.println(s);

    如果你想要按照字母排序,就在构造器中传入String.CASE_INSENSITIVE_ORDER

    Set<String> d = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);//按照字母排序
            
            d.add("a");
            d.add("b");
            d.add("c");
            d.add("d");
            d.add("A");
            d.add("B");
            d.add("C");
            d.add("D");
            
            System.out.println(d);

    Map

     将对象映射到其他对象的能力是Map的极大优势

    Map与数组和其他collection一样可以轻松扩展到多维,而我们只需要将其设置为map,而这些值可以使其他容器,甚至是其他map。因此,我们会很容易将容器组合起来从而  快速生成强大的数据结构。列如,假如你正在跟踪拥有多个宠物的人,你所需的只是一个map<person,List<pet>>

    例如,存储一个二维数组

    public ArrayList<Integer> getList() {
            ArrayList<Integer> s = new ArrayList<Integer>();
            
            Random rand = new Random(47);
            for (int i = 0; i < 5; i++) {
                s.add(rand.nextInt(30));
            }
            
            return s;
        }
    
        public static void main(String[] args) {
            // 存储一个多维数组
            ToMap k=new ToMap();
            Map<Integer, ArrayList<Integer>> m = new HashMap<Integer, ArrayList<Integer>>();
    
            m.put(1, k.getList());
            m.put(2, k.getList());
            m.put(3, k.getList());
            Set<Integer> keySet = m.keySet();//返回所有的键组成的set,经常被用来迭代map
            for(Integer a:keySet) {
                
                System.out.println(a);
                System.out.println(m.get(a));
            }
        }

    Queue

    队列是一个典型的先进先出(FIFO) 的容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,就像你将在以后所看到的,因为它们可以安全地将对象从一个任务传输给另一个任务。
    LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口public interface Deque<E> extends Queue<E>,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue,下面的示例使用了在Queue接口中与Queue相关的方法:
     
     
    @Test
        public void ToQueue() {
            Queue<Integer> q=new LinkedList<Integer>();
            Queue<Integer> q2=new LinkedList<Integer>();
            //Random rand=new Random(47);
            for(int i=0;i<10;i++) {//两者都是往队列尾部插入元素,不同的时候,当超出队列界限的时候,add()方法是抛出异常让你处理,而offer()方法是直接返回false
                //q.offer(i);
                q2.offer(i);
                q.add(i);
            }
            System.out.println(q);
            System.out.println(q2);
            while(q.peek()!=null&&q2.peek()!=null) {
                System.out.println(q.remove()+"  "+q2.remove());
            }
        }
    offer方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插人到队尾,或者返回false。peek0和elementO都将 在不移除的情况下返回队头,但是peek0方法在队列为空时返回null,而elementO会 拋出NoSuchElementException异常。pollO和remove0方法将移除并返回队头,但是poll0在队列为空时返回null,而remove0会 抛出NoSuchElementException异常。
    Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可以使用,因此,你能够访问的LinkedList的方法会变少
    注意,与Queue相关的方法提供了完整而独立的功能。即,对于Queue所继承的Collection,在不需要使用它的任何方法的情况下,就可以拥有一个可用的Queue
     
    还有另一个实现类PriorityQueue
    先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。
          优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。例如,在飞机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息系统,某些消息比其他消息更重要,因而应该更快地得到处理,那么它们何时得到处理就与它们何时到达无关。PriorityQueue添加到Java SE5中,是为了提供这种行为的一种自动实现。
          当你在PriorityQueue上调用offer方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue 可以确保当你调用peek,poll,和remove,方法时,获取的元素将是队列中优先级最高的元素。
          让PriorityQueue与Integer、String和Character这 样的内置类型一起工作易如反掌。在下面的示例中,
      
       public void ToPriorityQueue() {
            //会自动排序,默认自然排序,且最小的值有最高的优先值
            PriorityQueue<Integer> p=new PriorityQueue<Integer>();
            Random rad=new Random(47);
            for(int i=0;i<10;i++) {
                p.offer(rad.nextInt(i+20));
            }
            
            Set<Integer> s=new HashSet<Integer>();//会去除重复值,这样做只是为了增添一点乐趣
            for(Integer i:p) {
                s.add(i);
            }
            System.out.println(s);
            PriorityQueue<Integer> p2=new PriorityQueue<Integer>(s);
            System.out.println(p2);
            while(p.peek()!=null) {
                System.out.println(p.remove());
            }
        //Collections.reverseOrder()是产生的反序的Comparator
       PriorityQueue<String> p3=new PriorityQueue<String>(l.size(),Collections.reverseOrder());
       p3.addAll(l);
       while(p3.peek()!=null) {
            System.out.println(p3.remove());
          } }
    你可以看到,重复是允许的,最小的值拥有最高的优先级(如果是String,空格也可以算作值,并且比字母的优先级高)。为了展示你可以使用怎样的方法通过提供自己的Comparator对象来改变排序,p2对PriorityQueue<Integer>的构造器调用,和p3对PriorityQueue<String>的调用使用了由Collections.reverseOrder( 新添加到Java SE5中的)产生的反序的Comparator。
          最后一部分添加了一个HashSet来消除重复的Character,这么做只是为了增添点乐趣。
          Integer、String和Character 可以与PriorityQueue-起工作, 因为这些类已经内建了自然排序。如果你想在PriorityQueue中使用自己的类,就必须包括额外的功能以产生自然排序,或者必须提供自己的Comparator。
     

    Collection

    Collection是描述所有序列容器共性的根接口,它可能被人为是一个复数接口,因为它是要表示其他若干个容器的共性而出现的接口

    另外AbstractCollection提供了Collection的默认实现,使你可以创建AbstractCollection的子类型,而其中没有不必要的代码重复

    使用接口描述的一个理由是它可以使我们能够创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多的对象类型。因此,如果我编写的方法将接受一个Collection,那么该方法就可以应用于任何实现了Collection的类,这也就使得一个新类可以选择去实现Collection接口,以便我的方法可以使用它。但是,有一点很有趣,就是我们注意到标准C++类库中并没有其容器的任何公共基类一容器之间的所有共性都是通过迭代器达成的。在Java中,遵循C++的方式看起来似乎很明智,即用迭代器而不是Collection来表示容器之间的共性。但是,这两种方法绑定到了一起,因为实现Collection就意味着需要提供iterator方法:

    例如:

    public static void display(Collection<Person> p){
            for(Person s:p) {
                System.out.println(s);
            }
        }
    事实上,Collection要 更方便点, 因为它是Iterable类型,因此,在display(Collection)实现中, 可以使用foreach结构,从而使代码更加清晰。
          当你要实现一个不是Collection的外部类时,由于让它去实现Collection接口可能非常困难或麻烦,因此使用Iterator就会变得非常吸引人。例如,如果我们通过继承一个持有Person对象的类来创建一个Collection的实现,那么我们必须实现所有的Collection方法,即使我们在display方法中不必使用它们,也必须如此。尽管这可以通过继承AbstractCollection而很容易地实现,但是你无论如何还是要被强制去实现iterator和size方法,以便提供AbstractCollection没有实现,但是AbstractCollection中的其他方法会使用到的方法:
    public class AbstractCircle<E> extends AbstractCollection {
    
        @Override
        public Iterator<E> iterator() {
            // TODO Auto-generated method stub
            return new Iterator<E>() {
    
                @Override
                public boolean hasNext() {
                    // TODO Auto-generated method stub
                    return false;
                }
    
                @Override
                public E next() {
                    // TODO Auto-generated method stub
                    return null;
                }
                
            };
        }
    
        @Override
        public int size() {
            // TODO Auto-generated method stub
            
            return 0;
        }
    }

    从本例中,你可以看到,如果你实现Collection,就必须实现iterator(),并且只拿只实现iterator与继承AbstractCollection相比,花费的代价只有略微减少。但是,如果你的类已经继承了其他的类,那么你就不能再继承AbstractCollection了。在这种情况下,要实现Collection,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力就会显得容易得多了

    Foreach与迭代器

    到目前为止foreach主要应用于数组,但是它也可以应用于所有Collection对象。

    在Java SE5引入新的iterable接口,该接口包含一个产生iterator的iterator方法,并且iterator接口foreach用来在序列中移动。因此如果你创建了任何实现iterator的方法,都将可以把他应用于foreach

    例如:

    public class GetInterable implements Iterable<String>{
        private String[] words= {"Go","with","me","and","other"};
        @Override
        public Iterator<String> iterator() {
            // TODO Auto-generated method stub
            return new Iterator<String>() {
                private int index=0;
                @Override
                public boolean hasNext() {
                    // TODO Auto-generated method stub
                    return index<words.length;
                }
    
                @Override
                public String next() {
                    // TODO Auto-generated method stub
                    return words[index++];
                }
                
            };
        }
        
        public static void main(String[] args) {
            for(String s:new GetInterable()) {
                System.out.println(s);
            }
        }
    
    }
     iterator方法返回的是实现了Iterator <String>的匿名内部类的实例,该匿名内部类可以遍历数组中的所有单词。在主方法中,你可以看到IterableClass确实可以用于foreach语句中。
          在Java SE5中,大量的类都是Iterable类型,主要包括所有的Collection类(但是不包括各种Map)。例如,, 下面的代码可以显示所有的操作系统环境变量:
    /**
         * 显示所有的操作系统的环境变量
         */
        @Test
        public void getSys() {
            for(Map.Entry entry:System.getenv().entrySet()) {
                System.out.println(entry.getKey()+" : "+entry.getValue());
            }
        }

    System.getenv返回一个Map, entrySet0产 生一个 由Map.Entry的元素构成的Set, 并且这个Set是一个Iterable,因此它可以用于foreach循环。
    foreach语句可以用于数组或其他任何Iterable,但是这并不意味着数组肯定也是一个Iterable,而任何自动包装也不会自动发生

    如果现有一个Iterable类,你想要添加种或多种在foreach语句中使用这个类的方法,应该怎么做呢?例如,假设你希望可以选择以向前的方向或是向后的方向迭代一个单词列表。如果直接继承这个类,并覆盖iterator0方法,你只能替换现有的方法,而不能实现选择。
          一种解决方案是所谓适配器方法的惯用法。“适配器”部分来自于设计模式,因为你必须提供特定接口以满足foreach语句。当你有一个接口并需要另一个接口时,编写适配器就可以解决问题。这里,我希望在默认的前向迭代器的基础上,添加产生反向迭代器的能力,因此我不能使用覆盖,而是添加了一个能够产生Iterable对象的方法,该对象可以用于foreach语句。正如你所见,这使得我们可以提供多种使用foreach的方式:
    public class GetArray<T> extends ArrayList<T> {
        public GetArray(Collection<T> c){
            super(c);
        }
        
        public Iterable<T> reversed(){
            return new Iterable<T>() {
    
                @Override
                public Iterator<T> iterator() {
                    // TODO Auto-generated method stub
                    return new Iterator<T>() {
                        int current=size()-1;
                        @Override
                        public boolean hasNext() {
                            // TODO Auto-generated method stub
                            return current>-1;
                        }
    
                        @Override
                        public T next() {
                            // TODO Auto-generated method stub
                            return get(current--);
                        }
                        public void remove() {
                            throw new UnsupportedOperationException();
                        }
                    };
                }
                
            };
        }
    }
    public static void main(String[] args) {
                    
            GetArray<String> g=new GetArray<String>(Arrays.asList(words));
            for(String s:g) {
                System.out.println(s);
            }
            for(String s:g.reversed()) {//倒序
                System.out.println(s);
            }
        }

    如果直接将g对象置于foreach语句中,将得到默认的前向迭代器。但是如果在该对象上调用reversed方法就会产生不同的行为。通过这种方式,我们可以实例中添加两种适配器方法:

    public class MultIterableClass extends GetInterable {
        /**
         * 反序迭代
         * 
         * @return
         */
        public Iterable<String> reversed() {
            return new Iterable<String>() {
    
                @Override
                public Iterator<String> iterator() {
                    // TODO Auto-generated method stub
                    return new Iterator<String>() {
                        int current = words.length - 1;
    
                        @Override
                        public boolean hasNext() {
                            // TODO Auto-generated method stub
                            return current > -1;
                        }
    
                        @Override
                        public String next() {
                            // TODO Auto-generated method stub
                            return words[current--];
                        }
    
                        public void remove() {
                            throw new UnsupportedOperationException();
                        }
                    };
                }
    
            };
        }
    
        public Iterable<String> randomized() {
            return new Iterable<String>() {
    
                @Override
                public Iterator<String> iterator() {
                    // TODO Auto-generated method stub
                    List<String> shuffled = new ArrayList<String>(Arrays.asList(words));
                    //对指定的列表进行随机排列,假设随机性的来源是公平的,那么所有的排列都以相等的概率发生。
                    Collections.shuffle(shuffled, new Random(47));
    
                    return shuffled.iterator();
                }
    
            };
        }
    
        public static void main(String[] args) {
            MultIterableClass mic = new MultIterableClass();
            
            for (String s : mic.reversed()) {
                System.out.print(s + " ");
            }
            
            System.out.println();
            for (String s : mic.randomized()) {
                System.out.print(s + " ");
            }
            
            System.out.println();
            for (String s : mic) {
                System.out.print(s + " ");
            }
        }
    }
    注意,第二个方法randomized没有创建它自己的Iterator,而是直接返回被打乱的List中的Iterator。
          从输出中可以看到,Collection.shufle0方 法没有影响到原来的数组,而只是打乱了shuffled中的引用。之所以这样,只是因为randomized方法用一个ArrayList将Arrays.asList方法的结果包装了起来。如果这个由Arrays.asList方法产生的List被直接打乱,那么它就会修改底层的数组,就像下面这样:
     
    Random rand = new Random(47);
            Integer[] a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            
            List<Integer> list = new ArrayList<Integer>(Arrays.asList(a));
            System.out.println(list);
            Collections.shuffle(list, rand);
            System.out.println("打乱顺序:" + list);
            System.out.println("array: " + Arrays.toString(a));
            
            System.out.println();
            
            List<Integer> list2 = Arrays.asList(a);//会修改原来的数组
            System.out.println(list2);
            Collections.shuffle(list2, rand);
            System.out.println("打乱顺序:" + list2);
            System.out.println("array: " + Arrays.toString(a));

    在第一种情况中,Arrays.asList的输 出被传递给了ArrayList的构造器,这将创建一个引用a的元素的ArrayList,因此打乱这些引用不会修改该数组。但是,如果直接使用Arrays.asList(a)的结果,这种打乱就会修改a的顺序。意识到Arrays.asList产生的List对象会使用底层数组作为其物理实现是很重要的。只要你执行的操作会修改这个List,并且你不想原来的数组被修改,那么你就应该在另一个容器中创建一个副本

    总结

    一,Java提供了大量持有对象的方式:
          1)数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成, 其容量就不能改变。
          2) Collection保存单-的元素,而Map保存相关联的键值对。有了Java的泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且在从容器中获取元素时,不必进行类型转换。各种Collection和各种Map都可以在你向其中添加更多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间的双向转换。
          3)像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器。List能够自动扩充容量。
          4)如果要进行大量的随机访问,就使用ArrayList;如果要经常从表中间插入或删除元素,则应该使用LinkedList。
          5)各种Queue以及栈的行为,由LinkedList提供支持。
          6) Map是种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键” 始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
          7) Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插人顺序保存元素。
       8)新程序中不应该使用过时的Vector和Hashtable和Stack
     
    二,除了TreeSet之外的所有Set都拥有与Collection完全样的接口
    List和Collection存在着明显的不同,尽管List所要求的方法都在Collection中。另方面,在Queue接口中的方法都是独立的;在创建具有Queue功能的实现时,不需要使用Collection方法。最后,Map和Collection之间的唯重叠就是Map可以使 用entrySet0和values0方法来产生Collection。
          注意,标记接口java.util.RandomAccess附着到了ArrayList上,而没有附着到LinkedList上。这为想要根据所使用的特定的List而动态修改其行为的算法提供了信息。
          从面向对象的继承层次关系来看,这种组织结构确实有些奇怪。但是,当你了解了java.util中更多的有关容器的内容后,你就会看到除了继承结构有些奇怪外,还有更多的问题。容器类库一直以来都是设计难题一解决这些难题涉及到要去满足经常彼此之间互为牵制的各方面需求。因此你应该学会中庸之道。
          抛开这些问题,Java的容 器每天都会用到的工具,它可以使程序更简洁、更强大、更高效。在适应容器类库的某些方面之前,你确实得废点劲儿,但是我想你很快就会找到自己的路子,去获得和使用这个类库中的类。
  • 相关阅读:
    C语言课程设计 学生信息管理系统 (简单版)
    Windows 常用Cmd命令行 (持续更新...)
    C++课程设计 通讯录管理系统 原码及解析
    C++ Error 个人笔记(live)
    sql查询当天数据
    web.xml中load-on-startup标签的含义
    @Repository、@Service、@Controller 和 @Component
    SQL Server JDBC驱动中sqljdbc和sqljdbc4区别
    windows下设置/删除Tomcat的开机自启动
    【Python技巧系列】条件语句一行实现
  • 原文地址:https://www.cnblogs.com/QianYue111/p/10247614.html
Copyright © 2011-2022 走看看