Java编程思想 第11章持有对象 学习笔记

基本概念

  • Java容器类类库的用途:保存对象,划分为两个不同的概念:
    Collection:一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,Set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序。
    Map:一组成对的“键值对”对象,允许你使用键来查找值。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在一起。
  • 创建List
    List<Apple> apples = new ArrayList<>();  // 创建一个ArrayList,向上转型为List
    
    创建一个具体的类,将其转型为对应的接口,,然后其余的代码中都使用这个接口。
    但是,如果要使用该接口额外的方法,就不能将具体的类向上转型为该接口。
  • 添加元素
    public class SimpleCollection {
          
          
    	public static void main(String[] args) {
          
          
    		Collection<Integer> c = new ArrayList<Integer>();
    		for(int i = 0; i < 10; i++) {
          
          
    			c.add(i);   // 添加元素
    		}
    		for(Integer i: c) {
          
             // foreach遍历
    			System.out.print(i + ", ");
    		}
    	}
    }
    
  • 添加一组元素
    Collection.addAll()成员方法只能接受另一个Collection,用它将自己初始化,因此可以通过Arrays.List()来为这个构造器输入。但是,Collections.addAll()方法运行起来快得多,构建一个空的Collection,然后调用Collections.addAll()这种方式很方便,因此它是首选方式。
    可以直接使用Arrays.adList()输出,将其当作List,但这种情况下,底层是数组,没有重写add()delete()方法,因此不能进行修改操作,否则会报异常。
    public class AddingGroups {
          
          
    	public static void main(String[] args) {
          
          
    		// Arrays.asList接受一个数组或逗号分隔的元素列表,并将其转换为List对象
    		// ArrayList构造器将其转换为ArrayList
    		Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
    		// collection的内部方法,将所有元素添加到collection中
    		Integer[] moreInts = {
          
          6, 7, 8, 9, 10};
    		collection.addAll(Arrays.asList(moreInts));
    		// 通过工具类Collections,将以逗号分隔的数字,都加入到collection中。或将List所有元素都添加到collection中
    		Collections.addAll(collection, 11, 12, 13, 14, 15);
    		Collections.addAll(collection, moreInts);
    		// 将一串数字,转换成List
    		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    		List.set(1, 99);   // 将index为1的位置,值设为99
    	}
    }
    

List

  • 两种类型的List
    ArrayList:长于随机访问元素,但是在List中间插入和移除元素时较慢。
    LinkedList:在List中间插入和删除操作代价较低,提供了优化的顺序访问。但是随机访问相对较慢。
    List<Integer> list = new ArrayList<Integer>();
    list.add(1);  // 添加元素
    list.add(1);  // 添加元素
    list.add(2);  // 添加元素
    list.add(3);  // 添加元素
    list.remove(Integer.valueOf(1)); // 删除和指定对象相等的第一个对象,[1, 2, 3]
    list.remove(1); // 删除元素,以下标,[1,3]
    System.out.println(list.contains(3)); // true
    System.out.println(list.get(0));  // 获取下标为0的元素,但不删除, 1
    System.out.println(list.indexOf(1));  // 获取第一个对象为1的下标, 0
    
    list.add(5);  // 添加元素
    list.add(6);  // 添加元素
    list.add(7);  // 添加元素
    list.add(8);  // 添加元素
    System.out.println(list);  // [1, 3, 5, 6, 7, 8]
    System.out.println(list.subList(1, 4));   // 左闭右开的子List,[3, 5, 6]
    System.out.println(list.containsAll(Arrays.asList(1, 5, 3)));   // 是否包含所有元素,顺序不定
    
    list.add(1);  // 添加元素
    list.add(-1);  // 添加元素
    list.add(-7);  // 添加元素
    list.add(-8);  // 添加元素
    Collections.sort(list);  // 从小到大排列, [-8, -7, -1, 1, 1, 3, 5, 6, 7, 8]
    //list.sort(Comparator.naturalOrder()); // 从小到大排列,两者等效
    list.sort(Comparator.reverseOrder());  // 从大到小排列, [8, 7, 6, 5, 3, 1, 1, -1, -7, -8]
    
    Collections.shuffle(list);  // 随机打乱, [6, 5, 1, 8, -7, -8, 7, -1, 1, 3]
    
    list.removeAll(Arrays.asList(1, 3));   // 删除list里面所有满足条件的对象,[-7, 5, 6, -8, 8, 7, -1]
    
    list.set(1, 8); // 下标为1的元素,设为8, [-7, 8, 6, -8, 8, 7, -1]
    
    list.addAll(Arrays.asList(1, 2));  // 在尾部添加所有元素,[-7, 8, 6, -8, 8, 7, -1, 1, 2]
    list.addAll(1, Arrays.asList(4, 5));  // 在下标为1的位置插入所有元素,[-7, 4, 5, 8, 6, -8, 8, 7, -1, 1, 2]
    
    list.retainAll(Arrays.asList(1, 2, 3, 4, 6));  // 取交集,[4, 6, 1, 2]	
    
    Object[] objects = list.toArray();  // list转化为array
    Integer[] integers = list.toArray(new Integer[0]);// list转化为array,并且将类型也确定了
    
    list.clear(); // 清空, []
    System.out.println(list.isEmpty());  // true
    
    – 移除一个元素remove(),是用equals来查找对象的。其他查找的方法,也是使用equals
    – 当存在很多从中间插入的操作时add(int index, Integer element)addAll(int index, Collection<? extends Integer> c),考虑使用LinkedList而不是ArrayList
    subList()所产生的列表的幕后就是初始列表,因此对返回的列表的修改都会反映到初始列表中,反之亦然。
    retainAll()是一种有效的“交集”操作,并且会将结果保存在list
    toArray()方法,将任意的Collection()转换为一个数组。无参数版本返回的是Object数组,如果传递指定类型的数组,则会返回该类型的数组(内部将创建一个合适尺寸的数组保存结果)

迭代器

  • 迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员并不需要关心该序列底层的结构。此外,迭代器通常是轻量级对象:创建它的代价小。
    – 使用iterator()要求容器返回一个IteratorIterator将准备好返回序列的第一个元素
    – 使用next()获得序列的下一个元素
    – 使用hasNext()检查序列中是否还有元素
    – 使用remove()将新近返回的元素删除(即next()刚返回的对象)。调用remove()之前必须调用next()
    display()方法不包含任何有关它所遍历的序列的类型信息,能够将遍历序列的操作与序列底层的结构分离。因此,迭代器统一了对容器的访问方式
    List<Integer> list = new ArrayList<Integer>();
    Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    Iterator<Integer> iterator = list.iterator(); // 获取该容器的迭代器
    // 遍历
    while (iterator.hasNext()) {
          
          
      System.out.println(iterator.next());
    }
    
    iterator = list.iterator();
    for (int i = 0; i < 3; i++) {
          
          
      System.out.println(iterator.next());
      iterator.remove();
    }
    System.out.println(list); // [4, 5, 6, 7, 8, 9]
    
    // 创建一个display()方法,不必知晓容器确切的类型,就可以显示
    public void display(Iterator<?> iterator) {
          
          
        while (iterator.hasNext()) {
          
          
          System.out.println(iterator.next());
        }
    }
    
  • ListIterator
    ListIterator是一个更加强大的Iterator的子类型。可以双向移动还可以指向当前位置的前一个索引和后一个索引。
    – 还可以使用set()方法替换它访问的最后一个元素
    – 可以在开始导出iterator()的时候,加入参数index,实现一开始就指向第n个元素。
    List<Integer> list = new ArrayList<Integer>();
    Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    ListIterator<Integer> listIterator = list.listIterator();
    
    while (listIterator.hasNext()) {
          
          
      // 获取下一个值、下一个值的下标、上一个值的下标
      System.out.println("next():" + listIterator.next() + "  nextIndex():" + listIterator.nextIndex() +
              "  previousIndex():" + listIterator.previousIndex());
    }
    
    while (listIterator.hasPrevious()) {
          
          
      System.out.println("previous():" + listIterator.previous());
    }
    
    // 从坐标为3开始的迭代器
    listIterator = list.listIterator(3);
    while (listIterator.hasNext()) {
          
          
      // 获取下一个值、下一个值的下标、上一个值的下标
      System.out.println("next():" + listIterator.next() + "  nextIndex():" + listIterator.nextIndex() +
              "  previousIndex():" + listIterator.previousIndex());
    }
    

LinkedList

  • LinkedList还添加了用作栈、队列和双端队列的方法
  • 返回列表第一个元素:getFirst()element()完全一致,当List为空的时候,抛出NoSuchElementException异常。peek()方法也类似,只是当列表为空时返回null
  • 删除并返回第一个元素:removeFirstremove()完全移植,移除并返回第一个元素,当List为空的时候,抛出NoSuchElementException异常。poll()稍有差异,列表为空时返回null
  • 在尾部添加元素:add()addFirst()完全一样,在列表尾部加入元素
  • LinkedListadd()remove()有点像队列?先进先出。

Stack

  • 栈通常是值后进先出(LIFO)的容器,通常用LinkedList实现
  • 底层实现
    // 入栈
    public void push(E e) {
          
          
        addFirst(e);
    }
    // 出栈
    public E pop() {
          
          
        return removeFirst();
    }
    // 返回栈顶元素
    public E peek() {
          
          
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    // 
    

Set

  • Set不保存重复的元素。Set最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因如此,查找就成为了Set中最重要的操作。因此你通常都会选择一个HashSet的实现,它专门对快速查找进行优化。
  • Set具有与Collection完全一样的接口,因此没有任何额外的功能。只是行为不同(体现了继承性与多态性)
    Set<Integer> set = new HashSet<Integer>();
    Random rand = new Random();
    for (int i = 0; i < 1000; i++) {
          
          
      set.add(rand.nextInt(30));
    }
    System.out.println(set);
    
  • 处于速度原因的考虑,HashSet使用了散列。TreeSet存储在红黑树数据结构中。LinkedHashMap因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
  • 如果想对结果排序,一种方法是使用TreeSet
    Set<Integer> set = new TreeSet<>();
    set.add(53);
    set.add(1);
    set.add(4);
    set.add(-14);
    set.add(4);
    set.add(434);
    set.add(5);
    System.out.println(set);  // [-14, 1, 4, 5, 53, 434]
    
  • contains()测试Set的归属性
    Set<String> set = new HashSet<>();
    Collections.addAll(set, "4 4 523 54 fsf 3532 54".split(" "));
    System.out.println(set); // [4, 523, 3532, fsf, 54]
    System.out.println(set.contains("4"));  // true
    

Map

  • 将对象映射到其他对象
    Random rand = new Random(47);  // 生成随机数
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < 10000; i++) {
          
          
      int r = rand.nextInt(20);
      Integer freq = map.get(r);  // 获取目前的频率
      map.put(r, freq == null ? 1 : freq + 1);  // 如果目前还不存在,就存入1,否则在现在基础上+1
    }
    System.out.println(map);
    
  • containsKey()containsValue()
    Map<String, String> map = new HashMap<>();
    map.put("cat", "ketty");
    map.put("dog", "honey");
    System.out.println(map.containsKey("cat")); // 是否包含键, true
    System.out.println(map.containsValue("ketty"));  // 是否包含值, true
    
  • Map很容易拓展成多维。可以创建一个Map<Person, List<Pet>>的Map,每个值保存一个List
  • 获取所有键、值、键值对
    Set<String> strings = map.keySet();  // 获取所有键组成的Set
    Collection<String> values = map.values();  // 获取所有值组成的Collection
    Set<Map.Entry<String, String>> entries = map.entrySet();  // 获取所有键值对(Entry)组成的Set
    System.out.println(strings);
    System.out.println(values);
    for (Map.Entry<String, String> entry : entries) {
          
          
      String key = entry.getKey();
      String value = entry.getValue();
      System.out.println(key + " " + value);
    }
    

Queue

  • 队列是一个典型的先进先出(FIFO)的容器。LinkedList提供了以支持队列的行为,并且它实现了Queue接口,因此LinkedList用作Queue的一种实现。
  • offer()方法,它在允许情况下,将一个元素插入到队尾,或者返回falsepeek()element()都将在不移除的情况下,返回队头,但是peek()方法在队列为空的时候返回nullelement()会抛出NoSuchElementException异常。
  • poll()remove()都会移除队头并返回。在队列为空时,poll()会返回空,remove()会抛出NoSuchElementException异常。
    Queue<Integer> queue = new LinkedList<>();  // 向上转型
    for (int i = 0; i < 10; i++) {
          
          
      queue.offer(i);  // 在队尾加入值
    }
    System.out.println(queue.peek());  // 返回队头的值
    while (!queue.isEmpty()) {
          
          
      System.out.println(queue.poll());  // 返回队头的值,并移除
    }
    
  • 优先级队列
    – 优先级队列声明下一个弹出元素时最需要的元素(具有最高优先级)。
    – 当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中排序,默认的排序将使用对象在队列中的自然顺序(从小到大),但是可以提供自己的Comparator来修改这个顺序。
    PriorityQueue可以确保当你调用peek()poll()remove()方法时,获取的元素将是队列中优先级最高的元素。
    List<Integer> input = Arrays.asList(54, 3, 4, 5, 5, 45, 7, 5, 8);
    // 从小到大
    PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
    priorityQueue.addAll(input);
    System.out.println(priorityQueue);  // 不一定已经排好序
    while (!priorityQueue.isEmpty()) {
          
            // 但输出的,一定是排好序的
      System.out.println(priorityQueue.poll());
    }
    
    priorityQueue = new PriorityQueue<>(Collections.reverseOrder());  // 放一个反转自然顺序的Comparator进去,从大到小
    priorityQueue.addAll(input);
    System.out.println(priorityQueue);  // 不一定已经排好序
    while (!priorityQueue.isEmpty()) {
          
            // 但输出的,一定是排好序的
      System.out.println(priorityQueue.poll());
    }
    /*out
    [3, 5, 4, 5, 5, 45, 7, 54, 8]
    3
    4
    5
    5
    5
    7
    8
    45
    54
    [54, 8, 45, 5, 5, 4, 7, 3, 5]
    54
    45
    8
    7
    5
    5
    5
    4
    3
    
    // 根据字符串长度排序
    PriorityQueue<String> priorityQueue = new PriorityQueue<>(new Comparator<String>(){
          
          
      @Override
      public int compare(String o1, String o2) {
          
          
        return o1.length()- o2.length();  // 比较字符串长度
      }
    });
    priorityQueue.offer("amy");
    priorityQueue.offer("amyfaf");
    priorityQueue.offer("fadf");
    priorityQueue.offer("fadfa");
    while (!priorityQueue.isEmpty()) {
          
            // 但输出的,一定是排好序的
      System.out.println(priorityQueue.poll());
    }
    /* output
    amy
    fadf
    fadfa
    amyfaf
    */
    

Collection和Iterator

  • Collection是描述所有序列容器的共性的根接口。
  • 使用接口描述的一个理由是它可以使我们能够创建更通用的代码。通过针对接口而非具体实现来编写代码。
  • CollectionIterator均可以表示容器之间的共性
    public static <T> void display(Iterator<T> iterator) {
          
          
    while (iterator.hasNext()) {
          
          
      System.out.println(iterator.next());
    }
    }
    
    public static <T> void display(Collection<T> collection) {
          
          
    for (T c : collection) {
          
          
      System.out.println(c);
    }
    }
    
    两个版本的display()方法都可以及那个display()方法与底层容器的特定实现解耦。
  • 当你要实现一个不是Collection的外部类时,由于让它实现Collection接口可能非常困难或麻烦,因此使用Iterator就变得非常吸引人。实现Collection必须实现所有的Collection方法,即使不需要使用某些方法。而Iterator只需要实现两个方法。
    // Iterator底层
    public interface Iterator<E> {
          
          
        boolean hasNext();
        
        E next();
        
        default void remove() {
          
          
            throw new UnsupportedOperationException("remove");
        }
    
        default void forEachRemaining(Consumer<? super E> action) {
          
          
            Objects.requireNonNull(action);
            while (hasNext())
                action.accept(next());
        }
    }
    

Foreach与迭代器

  • 之所以foreach能够工作,是因为实现了Iterable接口,该接口包含一个能够产生Iteratoriterator()方法,并且Iterable接口被foreach用来在序列中移动
  • 因此,创建的任何实现了Iterable的类,都可以通过foreach遍历
    Collection<String> cs = new ArrayList<>();
    cs.addAll(Arrays.asList("faf fae gs hfdh".split(" ")));
    for(String s : cs) {
          
          
    	System.out.println(s);
    }
    
    public class IterableImpl implements Iterable<String> {
          
          
      String[] s = "faf gdagfa aff gag".split(" ");
    
      @Override
      public Iterator<String> iterator() {
          
          
        return new Iterator<String>() {
          
          
          private int index = 0;
    
          @Override
          public boolean hasNext() {
          
          
            return index < s.length;
          }
    
          @Override
          public String next() {
          
          
            return s[index++];
          }
        };
      }
    
      @Test
      public void test() {
          
          
        IterableImpl strings = new IterableImpl();
        for (String string : strings) {
          
          
          System.out.println(string);
        }
      }
    }
    
  • Collection接口继承了Iterable接口,从而实现了Iterable接口的几个方法

Arrays.asList()

  • 意识到Arrays.asList()产生的List对象会使用底层数组作为物理实现是很重要的。只要你执行的操作会修改这个List,并不想原来的数组被修改,那么你就应该在另一个容器上中创建一个副本。
    Random rand = new Random();
    Integer[] ia = {
          
          1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));
    Collections.shuffle(list1, rand);   // list1被打乱,但是ia并没有被打乱
    
    List<Integer> list2 = Arrays.asList(ia);
    Collections.shuffle(list2, rand);   // list2被打乱,ia也被打乱了
    

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42524843/article/details/113854084