Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

java基础(十)之持有对象

2017-06-10
nivelle

持有对象

在写程序时不知道将需要多少个对象,或者是否需要更复杂的方式来存储对象来存储对象,若用固定大小的数组来存储,就明显受限了.

java实用类库提供了一套完整的容器类解决这个问题,其中基本的类型是List,Set,Queue和Map.这些对象称为集合类,但由于java的类库中使用了Collection这个名字来指代该类库的一个特殊子集,所以我们使用了范围更广的称呼”容器”.

泛型和类型安全的容器

Object默认的toString()方法,将打印类名,后面跟随该对象的散列码的无符号十六进制表示.

基本概念

java容器类类库的用途是”保存对象”,并将其划分为不同的概念:

  • Collection:一个独立元素的序列,这些元素都服从一条或者多条规则.List必须按照插入的顺序保存元素,而set不能有重复元素.Queue按照排队规则来确定对象产生的顺序(通常与插入的顺序相同)
  • Map:一组成对的”键值对”对象,允许你使用键来查找值.映射表允许我们使用另一个对象来查找某个对象,它也被称为”关联数组”,因为它将某些对象与另外一些对象关联在了一起;或者称为”字典”,因为你可以使用键对象来查找值对象.

Collection接口概括了序列的概念:一种存放一组对象的方式.


import java.util.ArrayList;
import java.util.Collection;

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) {
      System.out.print(i);
    }
  }
}

因为这个示例只使用了Collection方法,因此任何继承自Collection的类的对象都可以正常工作,但是ArrayList是最基本的序列类型.

add()方法的名称就表明它是将一个新元素放置到Collection中.但是,文档中非常仔细地叙述到:”要确保这个Collection包含指定的元素”.这是考虑到了set的含义,因为在set中只有元素不存在的情况下才会添加.在使用ArrayList,或者任何类的List时,add()总是表示把他放进去,因为list不关心是否存在重复.

添加一组元素

  • Arrays.asList()接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),将其转变成为一个List对象.

  • Collection.addAll()方法接受一个Collection对象,以及一个数组或是一个逗号分隔的列表,将元素添加到Collection.

package learn;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

public class AddingGroups {
  public static void main(String[] args) {
    Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
    Integer[] moreInts = { 6, 7, 8, 9, 10 };
    collection.addAll(Arrays.asList(moreInts));

    Collections.addAll(collection, 11, 12, 13, 14, 15);

    List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
    collection.addAll(list);
    for (Integer i : collection) {
      System.out.print(i + ",");
    }
  }
}


Collection的构造器可以接受另一个Collection,用它来将自身初始化,因此你可以使用Arrays.List()来为这个构造器产生输入.但是,Collection.addAll()方法运行起来要快的多,而且构建一个不包含元素的Collection,然后调用Collection.add()这种方式很方便,因此它是首选.

容器的打印

你必须使用Arrays.toString()来产生数组的可打印表示,但是打印容器无需任何帮助.

package learn;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

public class PrintingContainers {
  static Collection<String> fill(Collection<String> collection) {
    collection.add("rat");
    collection.add("cat");
    collection.add("dog");
    collection.add("dog");
    return collection;
  }
  static Map<String,String> fill(Map<String, String> map){
    map.put("rat", "Fuzzy");
    map.put("cat", "Rags");
    map.put("dog", "Bosco");
    map.put("dog", "Spot");
    return map;
  }
  public static void main(String[] args) {
    System.out.println(fill(new ArrayList<String>()));
    System.out.println(fill(new LinkedList<String>()));
    System.out.println(fill(new HashSet<String>()));
    System.out.println(fill(new TreeSet<String>()));
    System.out.println(fill(new LinkedHashSet<String>()));
    System.out.println(fill(new HashMap<String,String>()));
    System.out.println(fill(new TreeMap<String,String>()));
    System.out.println(fill(new LinkedHashMap<String,String>()));
  }
}


结果:

[rat, cat, dog, dog]

[rat, cat, dog, dog]

[cat, dog, rat]

[cat, dog, rat]

[rat, cat, dog]

{cat=Rags, dog=Spot, rat=Fuzzy}

{cat=Rags, dog=Spot, rat=Fuzzy}

{rat=Fuzzy, cat=Rags, dog=Spot}


默认的打印行为,使用容器提供的toString()方法,即可生成可读性很好的结果.Collection打印出来的内容用方括号扩住,每个元素由逗号分隔.Map则用大括号扩住,键与值由等号联系.


ArrayList和LinkedList都是List类型,它们都是按照被插入的顺序保存元素. HashSet,treeSet,和LinkedHashSet都是Set类型.HashSet使用的是相当复杂的方式来存储元素的,这种技术是最快的获取元素的方式,因此,存储的顺序看起来并无实际意义.如果存储顺序很重要,那么可以使用TreeSet,它可以按照升序保存对象,或者使用LinkedHashSet,它按照被添加的顺序保存对象.


Map(关联数组)使得你可以用键来查找对象,就像一个简单的数据库.不必指定Map的尺寸,因为它自己会自动调整尺寸.Map还知道如何打印自己,它会显示相关联的键和值.HashMap提供了最快的查找技术,也没有按照任何顺序来保存其元素,TreeMap按照比较结果的升序保存键,而LinkedHashMap则按照插入保存键,同时还保留了HashMap的查询速度.

List

承诺将元素维护在特定的序列中,List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素.

  • ArrayList,长于随机访问元素,但是在List中间插入和移除元素时较慢.

  • LinkedList,它通过代价较低的在list中间进行的插入和删除操作,提供了优化的顺序访问.LinkedList在随机访问方面比较慢,但是的的特性集较ArrayList.


可以用contains()方法来确定某个对象是否在列表中,如果移除某个对象,则可以将这个对象的引用传递给remove()方法.同样如果有一个对象的引用,则可以使用indexOf()来发现该对象在List中所处位置的索引编号.

subList()方法允许你很容易地从较大的列表中创建一个片段,而将这个结果传递给较大列表的containsAll()方法时,自然会得到true.

迭代器

迭代器的概念可以用于实现切换不同的容器,迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关系该序列底层的结构.此外,迭代器被称为轻量级对象:创建它的代价小.Iterator的功能:

  • 使用Iterator()要求容器返回一个Iterator.Iterator将准备好返回序列的第一个元素.

  • 使用next()获得序列中的下一个元素.

  • 使用hasNext()检查序列中是否还有元素.

  • 使用remove()将迭代器新近返回的元素删除.


public class SimpleIteration{
  
  public static void main(String args[]){
     List<Pet> pets = Pets.arrayList(12);
     Iterator<Pet> it = pets.iterator();
     while(it.hasNext()){
        Pet p = it.next();
        System.out.println(p.id()+":"+p+" ");
     }
  }
}


接受对象容器并传递它,从而在每个对象上都执行操作,这种思想十分强大,并且贯穿于本书.

public static void display(Iterator<pet> it){
  while(it.hasNext()){
    Pet it = it.next();
  }
}

ListIterator

ListIerator 是一个更强大的Iterator子类型,只能用于各种List类的访问.尽管Iterator只能向前移动,但是ListIterator可以双向移动,它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素.可以通过调用ListIterator()方法产生一个指向List开始出的ListIterator,并且还可以通过调用ListIterator(n)创建一个一开始就指向列表索引为n的元素处的ListIterator.

package learn;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListFeatures {

  public static void main(String[] args) {
    List<Pet> pets = new ArrayList<Pet>();
    pets.add(new Pet(1, "cat"));
    pets.add(new Pet(2, "dog"));
    
    ListIterator<Pet> it = pets.listIterator();
    while(it.hasNext()){
      System.out.println(it.next());
      System.out.println(it.nextIndex()+","+it.previousIndex());
    }
    System.out.println();
    while(it.hasPrevious()){
      System.out.println(it.previous().getId()+"");
    }
    System.out.println(pets);
    it = pets.listIterator(1);
    while(it.hasNext()){
      it.next();
      it.set(new Pet(3, "fish"));
    }
    System.out.println(pets);
  }

}

class Pet {
  private int id;
  private String name;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Pet(int id, String name) {
    this.id = id;
    this.name = name;
  }
  
  @Override
  public String toString(){
    
    return "id:"+id+"  "+"name:"+name;
    
}
}

结果:

id:1 name:cat

1,0

id:2 name:dog

2,1

2

1

[id:1 name:cat, id:2 name:dog]

[id:1 name:cat, id:3 name:fish]

LinkedList

LiknedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(插入和移除)时比ArrayList更高效,但在随机访问操作方面稍逊色一点.

同时提供了一些用于栈\队列或双端队列的方法:

getFirst()和element()完全一样,它们都返回列表的第一个元素,且并不移除它,若列表为空,则返回NoSuchElementException.

peek()方法也返回头元素,但是若列表为空则返回null.

removeFirst()与remove()完全一样,它们移除并返回列表的头,而在列表为空时抛出NoSuchElementException.

poll()它会在列表为空时返回null.

addFirst()与add()和addLast()相同,它们都将某个元素插入到列表的尾部.

removeLast()移除并返回列表的最后一个元素.

Stack

“栈”通常是指”先进后出”的容器.LinkedList具有实现栈所有功能的方法,因此可以将LinkedList作为栈使用.

public class Stack<T> {
  private LinkedList<T> storage = new LinkedList<T>();

  public void push(T v) {
    storage.addFirst(v);
  }

  public T peek() {
    return storage.getFirst();
  }

  public T pop() {
    return storage.removeFirst();
  }

  public boolean empty() {
    return storage.isEmpty();
  }

  public String toString() {
    return storage.toString();
  }
}


类名之后的告诉编译器这将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,就是T.申明定义一个可以持有T类型对象的Stack.

Set

set不保存重复的元素,最常被用来使用的是测试归属性,可以很容易地询问某个对象是否在某个Set中.查找成为了Set中最重要的操作,选择一个HashSet,它专门对快速查找进行了优化.

Set具有与Cpllection完全一样的接口,没有额外的功能.实际上Set就是Collection,只是行为不同.


import java.util.HashSet;

import java.util.Random;

import java.util.Set;

public class SetOfInteger {

  public static void main(String[] args) {

    Random rand = new Random(47);

    Set<Integer> intset = new HashSet<>();

    for (int i = 0; i < 10000; i++) {

      intset.add(rand.nextInt(30));

    }

    System.out.println(intset);

  }

}


最常见的操作之一,就是使用contains()测试Set的归属性.

package learn;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SetOperations {

  public static void main(String[] args) {

    Set<String> set1 = new HashSet<>();

    Collections.addAll(set1, "A B C D E F G H I J K L".split(" "));

    set1.add("M");

    System.out.println("H:" + set1.contains("H"));

    System.out.println("N:" + set1.contains("N"));

    Set<String> set2 = new HashSet<>();

    Collections.addAll(set2, "H I J K L".split(" "));

    System.out.println("set2 in set1:" + set1.containsAll(set2));

    set1.remove("H");

    System.out.println("set1:" + set1);

    System.out.println("set2 in set1:" + set1.containsAll(set2));

    set1.removeAll(set2);

    System.out.println("set2 removed from set1:" + set1);

    Collections.addAll(set1, "X Y Z".split(" "));

    System.out.println(" 'x y z' added to set1: " + set1);
  }
}



Map

将对象映射到其他对象的能力是一种解决编程问题的杀手锏.

Map与数组和其他Collection一样,可以很容易扩展到多维,我们只需要将其设置为Map(这些Map的值可以是其他容器,甚至是其他Map).因此,我们很容易将容器组合起来从而快速地生成强大的数据结构.例如,假设真正追踪拥有多个宠物的人,你只需要一个Map<Person,List>.

Map可以返回它的键的Set,keySet(). 它的值的Collection.或者它的键值对的Set.

Queue

队列时一个典型的先进先出的容器.即从容器的一端放入事物,从另一端取出并且事物放入容器的顺序与取出的顺序是相同.

队列常被当作一种可靠的将对象从程序的某个区域传递到另一个区域的途径.

LinkedList提供了方法以支持队列行为,并且它实现了Queue接口,因此LinkedList可以作为Queue的一种实现.通过将LinkedList向上转型为Queue.

public class QueueDemo {
  public static void printQ(Queue queue){
    while(queue.peek()!=null){
      System.out.print(queue.remove()+" ");
    }
  }
  public static void main(String args[]){
    Queue<Integer>queue = new LinkedList<>();
    Random rand = new Random(47);
    for(int i=0;i<10;i++){
      queue.offer(rand.nextInt(i+10));
    }
    printQ(queue);
    Queue<Character> qc = new LinkedList<>();
    for(char c:"Brontosaurus".toCharArray()){
      qc.offer(c);
    }
    printQ(qc);
  }
  
}


结果:

8 1 1 1 5 14 3 1 0 1 B r o n t o s a u r u s


offer()方法,在允许情况下,将一个元素插入到队尾,或者返回false. peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常.

poll()和remove()方法将移除并返回对头,但是poll()在队列为空时返回null,而remove()会返回NoSuchElementException异常.

priorityQueue

优先级队列声明下一个弹出元素是最需要的元素(具有最高优先级)

当使用PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序.默认的排序将使用对象在队列中的自然顺序.但是可以通过自己的Comparator来修改这个顺序.

Collection和Iterator

Collection是描述所有序列容器的共性的根接口,它可能被认为是一个”附属接口”,因为要表示若干个接口的共性而出现的接口.另外AbstractCollection类提供了Collection的默认实现,使得可以创建Abstraction的子类型,其中没有不必要的代码重复.

在java中,用迭代器而不是Collection来表示容器之间的共性.这两种方法绑定到一起了,因为实现了Collection就意味着需要提供Iterator()方法:

** Iterable接口,该接口包含一个能够创建Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动.**

package learn;

import java.nio.channels.UnsupportedAddressTypeException;
import java.util.Iterator;

public class IterableClass implements Iterable<String> {

  protected String[] words = ("and that is how we know the earth to be banana-shaped").split("");

  @Override
  public Iterator<String> iterator() {

    return new Iterator<String>() {

      private int index = 0;

      public boolean hasNext() {
        return index < words.length;
      }

      @Override
      public String next() {

        return words[index++];
      }

      @Override
      public void remove() {
        throw new UnsupportedAddressTypeException();

      }
    };
  }

  public static void main(String args[]) {
    for (String s : new IterableClass()) {
      System.out.print(s + "");

    }
  }

}



结果:

and that is how we know the earth to be banana-shaped

forEach(0之所以能工作,就是因为引入了iterable接口. iterator()方法返回的是实现了Iterator的匿名内部类的实例,该匿名内部类可以遍历数组中的所有内容.


评论