Java基础:对象的集合(上)

来源:互联网 发布:菜鸟网络物流公司 编辑:程序博客网 时间:2024/05/17 22:34
 .数组:数组与其它容器的区别体现在三个方面:效率,类型识别以及可以持有primitives。数组是 Java 提供的,能随机存储和访问 reference 序列的诸多方法中的,最高效的一种。数组是一个简单的线性序列,所以它可以快速的访问其中的元素。但是速度是有代价的;当你创建了一个数组之后,它的容量就固定了,而且在其生命周期里不能改变。java泛型容器类还包括 ListSet Map。它们处理对象的时候就好像这些对象都没有自己的具体类型一样。也就是说,容器将它所含的元素都看成是(Java 中所有类的根类)Object 的。这样你只需创建一种容器,就能把所有类型的对象全都放进去。与其他泛型容器相比,这里体现出数组的第二个优势:创建数组的时候,你也同时指明了它所持有的对象的类型(这又引出了第三点 —— 数组可以持有 primitives,而容器却不行)。也就是说,它会在编译的时候作类型检查,从而防止你插入错误类型的对象,或者是在提取对象的时候把对象的类型给搞错了。

.数组是第一流的对象:不管你用的是那种类型的数组,数组的标识符实际上都是一个创建在堆(heap)里的实实在在的对象的”reference。实际上是那个对象持有其他对象的 referencereference(bject) 数组与primitives 数组不同的是对象数组持有 reference,而 primitive 数组则直接持有值。
import com.bruceeckel.simpletest.*;
class Weeble {} // A small mythical creature
public class ArraySize {
public static void main(String[] args) {
    // Arrays of objects:
    Weeble[] a; // Local uninitialized variable
    Weeble[] b = new Weeble[5]; // Null references
    Weeble[] c = new Weeble[4];//
数组的定义方式1
    for(int i = 0; i < c.length; i++)
       if(c[i] == null) // Can test for null
 reference
         c[i] = new Weeble( );
// Aggregate initialization:
    Weeble[] d = {new Weeble( ), new Weeble( ), new Weeble( )};//
数组的定义方式2
    // Dynamic aggregate initialization:
    a = new Weeble[] {new Weeble( ), new Weeble( )};//
数组的定义方式3
    System.out.println("a.length=" + a.length);
    System.out.println("b.length = " + b.length);
    // The references inside the array are
    // automatically initialized to null:
    for(int i = 0; i < b.length; i++)
       System.out.println("b[" + i + "]=" + b[i]);//
就算b里没有值也可以输出,只不过是null而已,
                            //
最重要的是Weeble[] b = new Weeble[5];分配了空间
    System.out.println("c.length = " + c.length);
    System.out.println("d.length = " + d.length);
    a = d;//
改变了a的指向
    System.out.println("a.length = " + a.length);
    // Arrays of primitives:
    int[] e; // Null reference
    int[] f = new int[5];//
数组的定义方式1
    int[] g = new int[4];
    for(int i = 0; i < g.length; i++)
       g[i] = i*i;
    int[] h = { 11, 47, 93 };//
数组的定义方式2
    // Compile error: variable e not initialized:
    //!System.out.println("e.length=" + e.length);
    System.out.println("f.length = " + f.length);
    // The primitives inside the array are
    // automatically initialized to zero:
    for(int i = 0; i < f.length; i++)
       System.out.println("f[" + i + "]=" + f[i]);
    System.out.println("g.length = " + g.length);
    System.out.println("h.length = " + h.length);
    e = h;//
改变了e的指向
    System.out.println("e.length = " + e.length);
    e = new int[] { 1, 2 };//
数组的定义方式3
    System.out.println("e.length = " + e.length);
   }
} ///:~

.返回一个数组:假设你写了一个方法,它返回的不是一个而是一组东西。在 C C++之类的语言里,这件事就有些难办了。因为你不能返回一个数组,你只能返回一个指向数组的指针。由于要处理控制数组生命周期之类的麻烦事,这么做很容易会出错,最后导致内存泄漏。Java 采取了类似的解决方案,但是不同之处在于,它返回的就是一个数组。与 C++不同,你永远也不必为 Java 的数组操心——只要你还需要它,它就还在;一旦你用完了,垃圾回收器会帮你把它打扫干净。(也就是说你可以把不同类型的东西同时都塞到一个数组里进行返回,只要你知道那个是哪个就行。这种情况也可以适合Java的其他容器里)
import com.bruceeckel.simpletest.*;
import java.util.*;
public class IceCream {
private static Random rand = new Random( );
public static final String[] flavors = {
     "Chocolate", "Strawberry", "Vanilla Fudge Swirl",
     "Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
     "Praline Cream", "Mud Pie"
};
public static String[] flavorSet(int n) {
     String[] results = new String[n];
     boolean[] picked = new boolean[flavors.length];
     for(int i = 0; i < n; i++) {
       int t;
       do
          t = rand.nextInt(flavors.length);
       while(picked[t]);
       results[i] = flavors[t];
       picked[t] = true;
     }
     return results;
}
public static void main(String[] args) {
     for(int i = 0; i < 20; i++) {
       System.out.println(
          "flavorSet(" + i + ") = ");
       String[] fl = flavorSet(flavors.length);
       for(int j = 0; j < fl.length; j++)
          System.out.println("/t" + fl[j]);
     }
}
} ///:~

.Arrays :java.util 里面有一个 Arrays 类,它包括了一组可用于数组的 static方法,这些方法都是一些实用工具。其中有四个基本方法:用来比较两个数组是否相等的 equals( );用来填充数组的 fill( );用来对数组进行排序的 sort( );以及用于在一个已排序的数组中查找元素的binarySearch( )。所有这些方法都对 primitive Object 进行了重载。此外还有一个 asList( )方法,它接受一个数组,然后把它转成一个List 容器。
(1)fill( )
方法,但是它太简单了;它只是简单的把一个的值复制到数组各个位置,如果是对象,则将相同的reference 拷贝到每个位置,:
int[] a5 = new int[10];
Arrays.fill(a5, 19);//a5
数组的每个位置都是19
String[] a9 = new String[10];
Arrays.fill(a9, "Hello");//a9
数组的每个位置都是"Hello"
Arrays.fill(a9, 3, 5, "World");//
"World"来填充a[3],a[4],包前不包后。
2java 标准类库提供了一个 System.arraycopy( ) static 方法。相比 for 循环,它能以更快的速度拷贝数组。如:
int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.arraycopy(i, 1, j, 0, i.length-1);
//把i考到j里,1i的下标,0j的开始下标,i.length-1j的结束下标
3Arrays 提供了经重载的 equals( )方法。当然,也是针对各种 primitive 以及 Object 的。两个数组要想完全相等,它们必须有相同数量的元素,而且数组的每个元素必须与另一个数组的相对应的位置上的元素相等。元素的相等性,用 eqauls( )判断。(对于 primitive,它会使用其 wrapper 类的 equals( );比如 int 使用Integer.equals( ))例如:
import com.bruceeckel.simpletest.*;
import java.util.*;
public class ComparingArrays {
public static void main(String[] args) {
    int[] a1 = new int[10];
    int[] a2 = new int[10];
    Arrays.fill(a1, 47);
    Arrays.fill(a2, 47);
    System.out.println(Arrays.equals(a1, a2));
    a2[3] = 11;
    System.out.println(Arrays.equals(a1, a2));
    String[] s1 = new String[5];
    Arrays.fill(s1, "Hi");
    String[] s2 = {"Hi", "Hi", "Hi", "Hi", "Hi"};
    System.out.println(Arrays.equals(s1, s2));
//数组是否相等是基于其内容,(通过 Object.equals( ))}
} ///:~

(4)Arrays.sort(a)
可以对数组a(a可以为任何的数组)进行排序.内置的排序方法能对任何数组排序,不论是 primitive的还是对象数组,只要它实现了 Comparable 接口或有一个与之相关的 Comparator(针对对象数组,primitive 数组不允许使用 Comparator)接口就行了。[1]:Comparable 接口,要重写一个方法 compareTo( ),这个方法能接受另一个对象作为参数,如果现有对象比参数小,它会返回一个负数,如果相同则返回零,如果现有的对象比参数大,它就返回一个正数。[2]Comparator 接口,有两个方法 compare( ) equals( )。但是除非是有特殊的性能要求,否则你用不着去实现 equals( )。因为只要是类,它就都隐含地继承自Object,而 Object 里面已经有了一个 equals( )了。所以你尽可以使用缺省的 Object equals( ),这样就已经满足接口的要求了。 Arrays.asList( )方法把数组改造成 List:
List a = Arrays.asList("one two three four five six seven eight".split(" "));

例子[1]:
import com.bruceeckel.util.*;
import java.util.*;
public class CompType implements Comparable {
int i;
int j;
public CompType(int n1, int n2) {
    i = n1;
    j = n2;
}
public String toString( ) {
    return "[i = " + i + ", j = " + j + "]";
}
public int compareTo(Object rv) {
    int rvi = ((CompType)rv).i;
    return (i < rvi ? -1 : (i == rvi ? 0 : 1));
}
private static Random r = new Random( );
public static Generator generator( ) {
    return new Generator( ) {
       public Object next( ) {
         return new CompType(r.nextInt(100),r.nextInt(100));
       }
    };
}
public static void main(String[] args) {
    CompType[] a = new CompType[10];
    Arrays2.fill(a, generator( ));
    System.out.println(
       "before sorting, a = " + Arrays.asList(a));
    Arrays.sort(a);
    System.out.println("after sorting, a = " + Arrays.asList(a));
}
} ///:~
如果CompType 没有实现 Comparable 接口,那么程序运行调用到sort( )的时候,就会引发一个 ClassCastException 错误。这是因为sort( )会把传给它的参数转换成 Comparable

例子[2]:
import com.bruceeckel.util.*;
import java.util.*;
class CompTypeComparator implements Comparator {
public int compare(Object o1, Object o2) {
    int j1 = ((CompType)o1).j;
    int j2 = ((CompType)o2).j;
    return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1));
}
}
public class ComparatorTest {
public static void main(String[] args) {
    CompType[] a = new CompType[10];
    Arrays2.fill(a, CompType.generator( ));
    System.out.println("before sorting, a = " + Arrays.asList(a));
    Arrays.sort(a, new CompTypeComparator( ));//
利用Comparator比较方法来排列a数组,
                                                     //
因为自动调用compare方法
    System.out.println("after sorting, a = " + Arrays.asList(a));
}
} ///:~

(5)
查询有序数组,一旦数组排完序,你就能用 Arrays.binarySearch( )进行快速查询了。但是切忌对一个尚未排序的数组使用 binarySearch( );因为这么做的结果是没意义的。如果 Arrays.binarySearch( )找到了,它就返回一个大于或等于 0
的值。否则它就返回一个负值,而这个负值要表达的意思是,如果你手动维护这个数组的话,这个值应该插在哪个位置。这个值就是:(插入点)-1, “插入点就是,在所有比要找的那个值更大值中,最小的那个值的下标,或者,如果数组中所有的值都比要找的值小,它就是a.size( )。如果数组里面有重复元素,那它不能保证会返回哪一个。如果排序的时候用到了 Comparator ,那么binarySearch( )的时候,也必须使用同一个 Comparator (用这个方法的重载版)
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class AlphabeticSearch {
public static void main(String[] args) {
    String[] sa = new String[30];
    Arrays2.fill(sa, new Arrays2.RandStringGenerator(5));
    CompTypeComparator comp = new CompTypeComparator( );
    Arrays.sort(sa, comp);
//利用了Comparator比较排序
    int index = Arrays.binarySearch(sa, sa[10],comp);
//也一定要用Comparator进行比较
    System.out.println("Index = " + index);
     }
} ///:~

(6)
容器简介:Java 的容器类分成两种基本类型。它们的区别就在,每个位置能放多少对象。Collection 只允许每个位置上放一个对象(这个名字有点误导,因为容器类库也常被统称为 collections)。它包括以一定顺序持有一组对象 List,以及只能允许添加不重复的对象 SetArrayList 是一种 List,而 HashSet 则是一种 Set。你可以用 add( )方法往Collection 里面加对象。Map 保存的是(key)—形式的 pair,很像是一个微型数据库。上面这段程序用了一种叫HashMap Map。如果你建了一个州和首府 Map,然后想查一下 Ohio 的首府在哪里,你就可以用它来找了。用法和用下标查数组是一样的。(Map 又被称为关联性数组associative array)你可以用 put( )方法往 Map 里面加元素。它接受键值形式 pair 作参数。
   List
会老老实实地持有你所输入的所有对象,既不做排序也不做编辑。Set 则每个对象只接受一次,而且还要用它自己的规则对元素进行重新排序(一般情况下,你关心的只是Set 包没包括某个对象,而不是它到底排在哪里——如果是那样,你最好还是用 List)Map 也不接收重复的 pair,至于是不是重复,要由key 来决定。此外,它也有它自己的内部排序规则,不会受输入顺序影响。如果插入顺序是很重要的,那你就只能使用 LinkedHashSet LinkedHashMap 了。
   Collection
Map 默认情况下的打印输出(使用容器类的 toString( )方法)的效果很不错
,所以我们就不再提供额外的打印支持了。打印出来的 Collection 会用方括号括起来,元素与元素之间用逗号分开。Map 会用花括号括起来,键和值之间用等号联起来(键在左边,值在右边)

(7).
容器的缺点:不知道对象的类型:Java 的容器有个缺点,就是往容器里面放对象的时候,会把对象的类型信息给弄丢了。这是因为开发容器类的程序员不会知道你要用它来保存什么类型的对象,而让容器仅只保存特定类型的对象又会影响它的通用性。所以容器被做成只持有 Object,也就是所有对象的根类的 reference,这样它就能持有任何类型的对象了。(当然不包括 primitive,因为它们不是对象,也没有继承别的对象。)这是一个很了不起的方案,只是:
[1]
由于在将对象放入容器的时候,它的类型信息被扔掉了,所以容器对能往里面加什么类型的对象没有限制。比方说,即使你想让它只持有 cat,别人也能很轻易地把 dog 放进去。   
[2]
由于对象的类型信息没了,容器只知道它持有的 Object reference,所以对象在使用之前(在容器里取出的时候)还必须进行类型转换。

(8)
迭代器:“迭代器(iterator)”的概念(又是一个设计模式)就是用来达成这种抽象的。迭代器是一个对象,它的任务是,能在让客户程序员在不知道,或者不关心他所处理的是什么样的底层序列结构的情况下,就能在一个对象序列中前后移动,并选取其中的对象。javaiterator可做的事情:
[1]
iterator( )方法叫容器传给你一个 Iterator 对象。第一次调用Iterator next( )方法的时候,它就会传给你序列中的第一个元素。
[2]
next( )方法获取序列中的下一个对象。
[3]
hasNext( )方法查询序列中是否还有其他对象。
[4]
remove( )方法删除迭代器所返回的最后一个元素。
package c11;
public class Cat {
private int catNumber;
public Cat(int i) { catNumber = i; }
public void id( ) {
    System.out.println("Cat #" + catNumber);
}
public String toString(){
      System.out.println("Cat''id =" + catNumber);
}
} ///:~

package c11;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class CatsAndDogs2 {
public static void main(String[] args) {
    List cats = new ArrayList( );//ArrayList
是一个能够自动扩展的数组,但是他是一个List
    for(int i = 0; i < 7; i++)
      cats.add(new Cat(i));
    Iterator e = cats.iterator( );//Iterator
的创建方式
    while(e.hasNext( ))//
先用hasNext( )进行判断
      ((Cat)e.next( )).id( );
      System.out.println(e.next());//
直接调用toString()方法.
}
} ///:~

(9)Collection
的功能:Collection 的所有功能,也就是你能用 Set List做什么事(不包括从 Object 自动继承过来的方法)
[1]boolean add(Object):
确保容器能持有你传给它的那个参数。如果没能把它加进去,就返回 false(这是个可选的方法,本章稍后会再作解释。)                         
[2]boolean addAll(Collection):
加入参数 Collection 所含的所有元素。只要加了元素,就返回 true(“可选”)
[3]void clear( ):
清除容器所保存的所有元素。(“可选”)
[4]boolean contains(Object):
如果容器持有参数 Object,就返回true
[5]boolean containsAll(Collection):
如果容器持有参数 Collection 所含的全部元素,就返回 true
[6]boolean isEmpty( ):
如果容器里面没有保存任何元素,就返回 true
[7]Iterator iterator( ):
返回一个可以在容器的各元素之间移动的 Iterator
[8]boolean remove(Object):
如果容器里面有这个参数 Object,那么就把其中的某一个给删了。只要删除过东西,就返回 true(“可选”)
[9]boolean removeAll(Collection):
删除容器里面所有参数 Collection 所包含的元素。只要删过东西,就返回true(“可选”)
[10]boolean retainAll(Collection):
只保存参数 Collection 所包括的元素(集合论中交集的概念)。如果发生过变化,则返回 true(“可选”)
[11]int size( ) :
返回容器所含元素的数量。
[12]Object[] toArray( ):
返回一个包含容器中所有元素的数组。
[13]Object[] toArray(Object[] a):
返回一个包含容器中所有元素的数组,且这个数组不是普通的 Object 数组,它的类型应该同参数数组 a 的类型相同(要做类型转换)

(10)List
的功能:ist 的基本用法是用 add( )加对象,用 get( )取对象,用iterator( )获取这个序列的 Iterator
[1]List (
接口):List 的最重要的特征就是有序;它会确保以一定的顺序保存元素。List Collection 的基础上添加了大量方法,使之能在序列中间插入和删除元素。(只对 LinkedList 推荐使用。)List 可以制造ListIterator 对象,你除了能用它在 List 的中间插入和删除元素之外,还能用它沿两个方向遍历List
[2]ArrayList*:
一个用数组实现的 List。能进行快速的随机访问,但是往列表中间插入和删除元素的时候比较慢。ListIterator 只能用在反向遍历 ArrayList 的场合,不要用它来插入和删除元素,因为相比LinkedList,在 ArrayList 里面用ListIterator 的系统开销比较高。
[3]LinkedList:
对顺序访问进行了优化。在 List 中间插入和删除元素的代价也不高。随机访问的速度相对较慢。(ArrayList 吧。)此外它还有 addFirst( )addLast( )getFirst( )getLast( )removeFirst( ) removeLast( )等方法(这些方法,接口和基类均未定义),你能把它当成栈(stack),队列(queue)或双向队列(deque)来用。

(11)Set
的功能:Set 的接口就是 Collection 的,所以不像那两个 List,它没有额外的功能。Set 会拒绝持有多个具有相同值的对象的实例(对象的又是由什么决定的呢?这个问题比较复杂,我们以后会讲的)
[1]Set (
接口):加入 Set 的每个元素必须是唯一的;否则,Set 是不会把它加进去的。要想加进 SetObject 必须定义 equals( ),这样才能标明对象的唯一性。Set 的接口和 Collection 的一模一样。Set 的接口不保证它会用哪种顺序来存储元素。
[2]HashSet*:
为优化查询速度而设计的 Set。要放进HashSet 里面的 Object 还得定义hashCode( )
[3]TreeSet:
是一个有序的 Set,其底层是一棵树。这样你就能从 Set 里面提取一个有序序列了。
[4]LinkedHashSet(JDK 1.4):
一个在内部使用链表的 Set,既有 HashSet 的查询速度,又能保存元素被加进去的顺序去(插入顺序)。用 Iterator 遍历 Set 的时候,它是按插入顺序进行访问的。
    HashSet
保存对象的顺序是和 TreeSet LinkedHashSet 不一样的。这是因为它们是用不同的方式来存储和查找元素的。(TreeSet 用了一种叫红黑树的数据结构『red-black treedata structure』来为元素排序,而 HashSet 则用了专为快速查找而设计的散列函数。LinkedHashSet 在内部用散列来提高查询速度,但是它看上去像是用链表来保存元素的插入顺序。)你写自己的类的时候,一定要记住,Set 要有一个判断以什么顺序来存储元素的标准,也就是说你必须实现 Comparable 接口,并且定义 compareTo( )方法。下面就是举例:
//: c11:Set2.java
// Putting your own type in a Set.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set2 {
   public static Set fill(Set a, int size) {
     for(int i = 0; i < size; i++)
       a.add(new MyType(i));
    return a;
}
public static void test(Set a) {
    fill(a, 10);
    fill(a, 10); // Try to add duplicates
    fill(a, 10);
    a.addAll(fill(new TreeSet( ), 10));
    System.out.println(a);
}
public static void main(String[] args) {
    test(new HashSet( ));
    test(new TreeSet( ));
    test(new LinkedHashSet( ));
}
} ///:~
无论是用哪种 Set,你都应该定义 equals( ),但是只有在要把对象放进 HashSet”的情况下,你才需要定义 hashCode( )(最好还是定义一个,因为通常情况下 HashSet Set 的首选)。但是作为一种编程风格,你应该在覆写 equals( )的同时把 hashCode( )也覆写了。
    SortedSet:SortedSet(
只有 TreeSet 这一个实现可用)中的元素一定是有序的。这使得 SortedSet 接口多了一些方法(注意,SortedSet 意思是根据对象的比较顺序,而不是插入顺序进行排序。)
[1]Comparator comparator( )
:返回 Set 所使用的 Comparator对象,或者用 null 表示它使用 Object 自有的排序方法。
[2]Object first( ):
返回最小的元素。
[3]Object last( ):
返回最大的元素。
[4]SortedSet subSet(fromElement, toElement):
返回 Set 的子集,其中的元素从 fromElement 开始到toElement 为止(包括fromElement,不包括 toElement)
[5]SortedSet headSet(toElement)
:返回 Set 的子集,其中的元素都应小于 toElement
[6]SortedSet headSet(toElement)
:返回 Set 的子集,其中的元素都应大于 fromElement
原创粉丝点击