溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java編程思想對象的容納實(shí)例詳解

發(fā)布時間:2020-08-29 11:12:50 來源:腳本之家 閱讀:130 作者:鬼步 欄目:編程語言

Java提供了容納對象(或者對象的句柄)的多種方式,接下來我們具體看看都有哪些方式。

有兩方面的問題將數(shù)組與其他集合類型區(qū)分開來:效率和類型。對于Java來說,為保存和訪問一系列對象(實(shí)際是對象的句柄)數(shù)組,最有效的方法莫過于數(shù)組。數(shù)組實(shí)際代表一個簡單的線性序列,它使得元素的訪問速度非???,但我們卻要為這種速度付出代價:創(chuàng)建一個數(shù)組對象時,它的大小是固定的,而且不可在那個數(shù)組對象的“存在時間”內(nèi)發(fā)生改變??蓜?chuàng)建特定大小的一個數(shù)組,然后假如用光了存儲空間,就再創(chuàng)建一個新數(shù)組,將所有句柄從舊數(shù)組移到新數(shù)組。這屬于“矢量”(Vector)類的行為。然而,由于為這種大小的靈活性要付出較大的代價,所以我們認(rèn)為矢量的效率并沒有數(shù)組高。

   在Java中,無論使用的是數(shù)組還是集合,都會進(jìn)行范圍檢查——若超過邊界,就會獲得一個RuntimeException(運(yùn)行期違例)錯誤。幾種常見的集合類:Vector(矢量)、Stack(堆棧)以及Hashtable(散列表)。這些類都涉及對對象的處理——好象它們沒有特定的類型。換言之,它們將其當(dāng)作Object類型處理(Object類型是Java中所有類的“根”類)。從某個角度看,這種處理方法是非常合理的:我們僅需構(gòu)建一個集合,然后任何Java對象都可以進(jìn)入那個集合(除基本數(shù)據(jù)類型外——可用Java的基本類型封裝類將其作為常數(shù)置入集合,或者將其封裝到自己的類內(nèi),作為可以變化的值使用)。這再一次反映了數(shù)組優(yōu)于常規(guī)集合:創(chuàng)建一個數(shù)組時,可令其容納一種特定的類型。

  對象數(shù)組容納的是句柄,而基本數(shù)據(jù)類型數(shù)組容納的是具體的數(shù)值。

1、集合

   為容納一組對象,最適宜的選擇應(yīng)當(dāng)是數(shù)組。而且假如容納的是一系列基本數(shù)據(jù)類型,更是必須采用數(shù)組。當(dāng)我們編寫程序時,通常并不能確切地知道最終需要多少個對象。有些時候甚至想用更復(fù)雜的方式來保存對象。為解決這個問題,Java提供了四種類型的“集合類”:Vector(矢量)、BitSet(位集)、Stack(堆棧)以及Hashtable(散列表)。與擁有集合功能的其他語言相比,盡管這兒的數(shù)量顯得相當(dāng)少,但仍然能用它們解決數(shù)量驚人的實(shí)際問題。

這些集合類具有形形色色的特征。例如,Stack實(shí)現(xiàn)了一個LIFO(先入先出)序列,而Hashtable是一種“關(guān)聯(lián)數(shù)組”,允許我們將任何對象關(guān)聯(lián)起來。除此以外,所有Java集合類都能自動改變自身的大小。所以,我們在編程時可使用數(shù)量眾多的對象,同時不必?fù)?dān)心會將集合弄得有多大。

  1.1 缺點(diǎn):類型未知

使用Java集合的“缺點(diǎn)”是在將對象置入一個集合時丟失了類型信息。之所以會發(fā)生這種情況,是由于當(dāng)初編寫集合時,那個集合的程序員根本不知道用戶到底想把什么類型置入集合。若指示某個集合只允許特定的類型,會妨礙它成為一個“常規(guī)用途”的工具,為用戶帶來麻煩。為解決這個問題,集合實(shí)際容納的是類型為Object的一些對象的句柄。這種類型當(dāng)然代表Java中的所有對象,因為它是所有類的根。當(dāng)然,也要注意這并不包括基本數(shù)據(jù)類型,因為它們并不是從“任何東西”繼承來的。這是一個很好的方案,只是不適用下述場合:

    (1) 將一個對象句柄置入集合時,由于類型信息會被拋棄,所以任何類型的對象都可進(jìn)入我們的集合——即便特別指示它只能容納特定類型的對象。舉個例子來說,雖然指示它只能容納貓,但事實(shí)上任何人都可以把一條狗扔進(jìn)來。

    (2) 由于類型信息不復(fù)存在,所以集合能肯定的唯一事情就是自己容納的是指向一個對象的句柄。正式使用它之前,必須對其進(jìn)行造型,使其具有正確的類型。

2、Stack

Stack有時也可以稱為“后入先出”(LIFO)集合。換言之,我們在堆棧里最后“壓入”的東西將是以后第一個“彈出”的。和其他所有Java集合一樣,我們壓入和彈出的都是“對象”,所以必須對自己彈出的東西進(jìn)行“造型”。
一種很少見的做法是拒絕使用Vector作為一個Stack的基本構(gòu)成元素,而是從Vector里“繼承”一個Stack。這樣一來,它就擁有了一個Vector的所有特征及行為,另外加上一些額外的Stack行為。很難判斷出設(shè)計者到底是明確想這樣做,還是屬于一種固有的設(shè)計。

下面是一個簡單的堆棧示例,它能讀入數(shù)組的每一行,同時將其作為字串壓入堆棧。

import java.util.*;
public class Stacks {
 static String[] months = { 
  "January", "February", "March", "April",
  "May", "June", "July", "August", "September",
  "October", "November", "December" };
 public static void main(String[] args) {
  Stack stk = new Stack();
  for(int i = 0; i < months.length; i++)
   stk.push(months[i] + " ");
  System.out.println("stk = " + stk);
  // Treating a stack as a Vector:
  stk.addElement("The last line");
  System.out.println(
   "element 5 = " + stk.elementAt(5));
  System.out.println("popping elements:");
  while(!stk.empty())
   System.out.println(stk.pop());
 }
}

months數(shù)組的每一行都通過push()繼承進(jìn)入堆棧,稍后用pop()從堆棧的頂部將其取出。要聲明的一點(diǎn)是,Vector操作亦可針對Stack對象進(jìn)行。這可能是由繼承的特質(zhì)決定的——Stack“屬于”一種Vector。因此,能對Vector進(jìn)行的操作亦可針對Stack進(jìn)行,例如elementAt()方法。

3、Hashtable

 Vector允許我們用一個數(shù)字從一系列對象中作出選擇,所以它實(shí)際是將數(shù)字同對象關(guān)聯(lián)起來了。但假如我們想根據(jù)其他標(biāo)準(zhǔn)選擇一系列對象呢?堆棧就是這樣的一個例子:它的選擇標(biāo)準(zhǔn)是“最后壓入堆棧的東西”。這種“從一系列對象中選擇”的概念亦可叫作一個“映射”、“字典”或者“關(guān)聯(lián)數(shù)組”。從概念上講,它看起來象一個Vector,但卻不是通過數(shù)字來查找對象,而是用另一個對象來查找它們!這通常都屬于一個程序中的重要進(jìn)程。

在Java中,這個概念具體反映到抽象類Dictionary身上。該類的接口是非常直觀的size()告訴我們其中包含了多少元素;isEmpty()判斷是否包含了元素(是則為true);put(Object key, Object value)添加一個值(我們希望的東西),并將其同一個鍵關(guān)聯(lián)起來(想用于搜索它的東西);get(Object key)獲得與某個鍵對應(yīng)的值;而remove(Object Key)用于從列表中刪除“鍵-值”對。還可以使用枚舉技術(shù):keys()產(chǎn)生對鍵的一個枚舉(Enumeration);而elements()產(chǎn)生對所有值的一個枚舉。這便是一個Dictionary(字典)的全部。

Dictionary的實(shí)現(xiàn)過程并不麻煩。下面列出一種簡單的方法,它使用了兩個Vector,一個用于容納鍵,另一個用來容納值:

import java.util.*;
public class AssocArray extends Dictionary {
 private Vector keys = new Vector();
 private Vector values = new Vector();
 public int size() { return keys.size(); }
 public boolean isEmpty() {
  return keys.isEmpty();
 }
 public Object put(Object key, Object value) {
  keys.addElement(key);
  values.addElement(value);
  return key;
 }
 public Object get(Object key) {
  int index = keys.indexOf(key);
  // indexOf() Returns -1 if key not found:
  if(index == -1) return null;
  return values.elementAt(index);
 }
 public Object remove(Object key) {
  int index = keys.indexOf(key);
  if(index == -1) return null;
  keys.removeElementAt(index);
  Object returnval = values.elementAt(index);
  values.removeElementAt(index);
  return returnval;
 }
 public Enumeration keys() {
  return keys.elements();
 }
 public Enumeration elements() {
  return values.elements();
 }
 // Test it:
 public static void main(String[] args) {
  AssocArray aa = new AssocArray();
  for(char c = 'a'; c <= 'z'; c++)
   aa.put(String.valueOf(c),
       String.valueOf(c)
       .toUpperCase());
  char[] ca = { 'a', 'e', 'i', 'o', 'u' };
  for(int i = 0; i < ca.length; i++)
   System.out.println("Uppercase: " +
       aa.get(String.valueOf(ca[i])));
 }
}

在對AssocArray的定義中,我們注意到的第一個問題是它“擴(kuò)展”了字典。這意味著AssocArray屬于Dictionary的一種類型,所以可對其發(fā)出與Dictionary一樣的請求。如果想生成自己的Dictionary,而且就在這里進(jìn)行,那么要做的全部事情只是填充位于Dictionary內(nèi)的所有方法(而且必須覆蓋所有方法,因為它們——除構(gòu)建器外——都是抽象的)。

Vector key和value通過一個標(biāo)準(zhǔn)索引編號鏈接起來。也就是說,如果用“roof”的一個鍵以及“blue”的一個值調(diào)用put()——假定我們準(zhǔn)備將一個房子的各部分與它們的油漆顏色關(guān)聯(lián)起來,而且AssocArray里已有100個元素,那么“roof”就會有101個鍵元素,而“blue”有101個值元素。而且要注意一下get(),假如我們作為鍵傳遞“roof”,它就會產(chǎn)生與keys.index.Of()的索引編號,然后用那個索引編號生成相關(guān)的值矢量內(nèi)的值。

main()中進(jìn)行的測試是非常簡單的;它只是將小寫字符轉(zhuǎn)換成大寫字符,這顯然可用更有效的方式進(jìn)行。但它向我們揭示出了AssocArray的強(qiáng)大功能。

標(biāo)準(zhǔn)Java庫只包含Dictionary的一個變種,名為Hashtable(散列表,注釋③)。Java的散列表具有與AssocArray相同的接口(因為兩者都是從Dictionary繼承來的)。但有一個方面卻反映出了差別:執(zhí)行效率。若仔細(xì)想想必須為一個get()做的事情,就會發(fā)現(xiàn)在一個Vector里搜索鍵的速度要慢得多。但此時用散列表卻可以加快不少速度。不必用冗長的線性搜索技術(shù)來查找一個鍵,而是用一個特殊的值,名為“散列碼”。散列碼可以獲取對象中的信息,然后將其轉(zhuǎn)換成那個對象“相對唯一”的整數(shù)(int)。所有對象都有一個散列碼,而hashCode()是根類Object的一個方法。Hashtable獲取對象的hashCode(),然后用它快速查找鍵。這樣可使性能得到大幅度提升(④)。散列表的具體工作原理已超出了本書的范圍(⑤)——大家只需要知道散列表是一種快速的“字典”(Dictionary)即可,而字典是一種非常有用的工具。

③:如計劃使用RMI(在第15章詳述),應(yīng)注意將遠(yuǎn)程對象置入散列表時會遇到一個問題(參閱《Core Java》,作者Conrell和Horstmann,Prentice-Hall 1997年出版)

④:如這種速度的提升仍然不能滿足你對性能的要求,甚至可以編寫自己的散列表例程,從而進(jìn)一步加快表格的檢索過程。這樣做可避免在與Object之間進(jìn)行造型的時間延誤,也可以避開由Java類庫散列表例程內(nèi)建的同步過程。

⑤:我的知道的最佳參考讀物是《Practical Algorithms for Programmers》,作者為Andrew Binstock和John Rex,Addison-Wesley 1995年出版。

作為應(yīng)用散列表的一個例子,可考慮用一個程序來檢驗Java的Math.random()方法的隨機(jī)性到底如何。在理想情況下,它應(yīng)該產(chǎn)生一系列完美的隨機(jī)分布數(shù)字。但為了驗證這一點(diǎn),我們需要生成數(shù)量眾多的隨機(jī)數(shù)字,然后計算落在不同范圍內(nèi)的數(shù)字多少。散列表可以極大簡化這一工作,因為它能將對象同對象關(guān)聯(lián)起來(此時是將Math.random()生成的值同那些值出現(xiàn)的次數(shù)關(guān)聯(lián)起來)。如下所示:

//: Statistics.java
// Simple demonstration of Hashtable
import java.util.*;

class Counter { 
 int i = 1; 
 public String toString() { 
  return Integer.toString(i); 
 }
}
class Statistics {
 public static void main(String[] args) {
  Hashtable ht = new Hashtable();
  for(int i = 0; i < 10000; i++) {
   // Produce a number between 0 and 20:
   Integer r = 
    new Integer((int)(Math.random() * 20));
   if(ht.containsKey(r))
    ((Counter)ht.get(r)).i++;
   else
    ht.put(r, new Counter());
  }
  System.out.println(ht);
 }
}

在main()中,每次產(chǎn)生一個隨機(jī)數(shù)字,它都會封裝到一個Integer對象里,使句柄能夠隨同散列表一起使用(不可對一個集合使用基本數(shù)據(jù)類型,只能使用對象句柄)。containKey()方法檢查這個鍵是否已經(jīng)在集合里(也就是說,那個數(shù)字以前發(fā)現(xiàn)過嗎?)若已在集合里,則get()方法獲得那個鍵關(guān)聯(lián)的值,此時是一個Counter(計數(shù)器)對象。計數(shù)器內(nèi)的值i隨后會增加1,表明這個特定的隨機(jī)數(shù)字又出現(xiàn)了一次。

假如鍵以前尚未發(fā)現(xiàn)過,那么方法put()仍然會在散列表內(nèi)置入一個新的“鍵-值”對。在創(chuàng)建之初,Counter會自己的變量i自動初始化為1,它標(biāo)志著該隨機(jī)數(shù)字的第一次出現(xiàn)。

為顯示散列表,只需把它簡單地打印出來即可。Hashtable toString()方法能遍歷所有鍵-值對,并為每一對都調(diào)用toString()。Integer toString()是事先定義好的,可看到計數(shù)器使用的toString。一次運(yùn)行的結(jié)果(添加了一些換行)如下:

{19=526, 18=533, 17=460, 16=513, 15=521, 14=495,
 13=512, 12=483, 11=488, 10=487, 9=514, 8=523,
 7=497, 6=487, 5=480, 4=489, 3=509, 2=503, 1=475,
 0=505}

大家或許會對Counter類是否必要感到疑惑,它看起來似乎根本沒有封裝類Integer的功能。為什么不用int或Integer呢?事實(shí)上,由于所有集合能容納的僅有對象句柄,所以根本不可以使用整數(shù)。學(xué)過集合后,封裝類的概念對大家來說就可能更容易理解了,因為不可以將任何基本數(shù)據(jù)類型置入集合里。然而,我們對Java封裝器能做的唯一事情就是將其初始化成一個特定的值,然后讀取那個值。也就是說,一旦封裝器對象已經(jīng)創(chuàng)建,就沒有辦法改變一個值。這使得Integer封裝器對解決我們的問題毫無意義,所以不得不創(chuàng)建一個新類,用它來滿足自己的要求。

 3.1 創(chuàng)建“關(guān)鍵”類

在前面的例子里,我們用一個標(biāo)準(zhǔn)庫的類(Integer)作為Hashtable的一個鍵使用。作為一個鍵,它能很好地工作,因為它已經(jīng)具備正確運(yùn)行的所有條件。但在使用散列表的時候,一旦我們創(chuàng)建自己的類作為鍵使用,就會遇到一個很常見的問題。例如,假設(shè)一套天氣預(yù)報系統(tǒng)將Groundhog(土拔鼠)對象匹配成Prediction(預(yù)報)。這看起來非常直觀:我們創(chuàng)建兩個類,然后將Groundhog作為鍵使用,而將Prediction作為值使用。如下所示:

//: SpringDetector.java
// Looks plausible, but doesn't work right.
import java.util.*;
class Groundhog {
 int ghNumber;
 Groundhog(int n) { ghNumber = n; }
}
class Prediction {
 boolean shadow = Math.random() > 0.5;
 public String toString() {
  if(shadow)
   return "Six more weeks of Winter!";
  else
   return "Early Spring!";
 }
}
public class SpringDetector {
 public static void main(String[] args) {
  Hashtable ht = new Hashtable();
  for(int i = 0; i < 10; i++)
   ht.put(new Groundhog(i), new Prediction());
  System.out.println("ht = " + ht + "\n");
  System.out.println(
   "Looking up prediction for groundhog #3:");
  Groundhog gh = new Groundhog(3);
  if(ht.containsKey(gh))
   System.out.println((Prediction)ht.get(gh));
 }
}

每個Groundhog都具有一個標(biāo)識號碼,所以赤了在散列表中查找一個Prediction,只需指示它“告訴我與Groundhog號碼3相關(guān)的Prediction”。Prediction類包含了一個布爾值,用Math.random()進(jìn)行初始化,以及一個toString()為我們解釋結(jié)果。在main()中,用Groundhog以及與它們相關(guān)的Prediction填充一個散列表。散列表被打印出來,以便我們看到它們確實(shí)已被填充。隨后,用標(biāo)識號碼為3的一個Groundhog查找與Groundhog #3對應(yīng)的預(yù)報。

看起來似乎非常簡單,但實(shí)際是不可行的。問題在于Groundhog是從通用的Object根類繼承的(若當(dāng)初未指定基礎(chǔ)類,則所有類最終都是從Object繼承的)。事實(shí)上是用Object的hashCode()方法生成每個對象的散列碼,而且默認(rèn)情況下只使用它的對象的地址。所以,Groundhog(3)的第一個實(shí)例并不會產(chǎn)生與Groundhog(3)第二個實(shí)例相等的散列碼,而我們用第二個實(shí)例進(jìn)行檢索。

大家或許認(rèn)為此時要做的全部事情就是正確地覆蓋hashCode()。但這樣做依然行不能,除非再做另一件事情:覆蓋也屬于Object一部分的equals()。當(dāng)散列表試圖判斷我們的鍵是否等于表內(nèi)的某個鍵時,就會用到這個方法。同樣地,默認(rèn)的Object.equals()只是簡單地比較對象地址,所以一個Groundhog(3)并不等于另一個Groundhog(3)。

因此,為了在散列表中將自己的類作為鍵使用,必須同時覆蓋hashCode()和equals(),就象下面展示的那樣:

//: SpringDetector2.java
// If you create a class that's used as a key in
// a Hashtable, you must override hashCode()
// and equals().
import java.util.*;
class Groundhog2 {
 int ghNumber;
 Groundhog2(int n) { ghNumber = n; }
 public int hashCode() { return ghNumber; }
 public boolean equals(Object o) {
  return (o instanceof Groundhog2)
   && (ghNumber == ((Groundhog2)o).ghNumber);
 }
}
public class SpringDetector2 {
 public static void main(String[] args) {
  Hashtable ht = new Hashtable();
  for(int i = 0; i < 10; i++)
   ht.put(new Groundhog2(i),new Prediction());
  System.out.println("ht = " + ht + "\n");
  System.out.println(
   "Looking up prediction for groundhog #3:");
  Groundhog2 gh = new Groundhog2(3);
  if(ht.containsKey(gh))
   System.out.println((Prediction)ht.get(gh));
 }
}

注意這段代碼使用了來自前一個例子的Prediction,所以SpringDetector.java必須首先編譯,否則就會在試圖編譯SpringDetector2.java時得到一個編譯期錯誤。

Groundhog2.hashCode()將土拔鼠號碼作為一個標(biāo)識符返回(在這個例子中,程序員需要保證沒有兩個土拔鼠用同樣的ID號碼并存)。為了返回一個獨(dú)一無二的標(biāo)識符,并不需要hashCode(),equals()方法必須能夠嚴(yán)格判斷兩個對象是否相等。
equals()方法要進(jìn)行兩種檢查:檢查對象是否為null;若不為null,則繼續(xù)檢查是否為Groundhog2的一個實(shí)例(要用到instanceof關(guān)鍵字)。即使為了繼續(xù)執(zhí)行equals(),它也應(yīng)該是一個Groundhog2。正如大家看到的那樣,這種比較建立在實(shí)際ghNumber的基礎(chǔ)上。這一次一旦我們運(yùn)行程序,就會看到它終于產(chǎn)生了正確的輸出(許多Java庫的類都覆蓋了hashcode()和equals()方法,以便與自己提供的內(nèi)容適應(yīng))。
 

  3.2 屬性:Hashtable的一種類型

  在第一個例子中,我們使用了一個名為Properties(屬性)的Hashtable類型。在那個例子中,下述程序行:

Properties p = System.getProperties();
p.list(System.out);


調(diào)用了一個名為getProperties()的static方法,用于獲得一個特殊的Properties對象,對系統(tǒng)的某些特征進(jìn)行描述。list()屬于Properties的一個方法,可將內(nèi)容發(fā)給我們選擇的任何流式輸出。也有一個save()方法,可用它將屬性列表寫入一個文件,以便日后用load()方法讀取。

盡管Properties類是從Hashtable繼承的,但它也包含了一個散列表,用于容納“默認(rèn)”屬性的列表。所以假如沒有在主列表里找到一個屬性,就會自動搜索默認(rèn)屬性。

Properties類亦可在我們的程序中使用(第17章的ClassScanner.java便是一例)。在Java庫的用戶文檔中,往往可以找到更多、更詳細(xì)的說明。

4、 再論枚舉器

我們現(xiàn)在可以開始演示Enumeration(枚舉)的真正威力:將穿越一個序列的操作與那個序列的基礎(chǔ)結(jié)構(gòu)分隔開。在下面的例子里,PrintData類用一個Enumeration在一個序列中移動,并為每個對象都調(diào)用toString()方法。此時創(chuàng)建了兩個不同類型的集合:一個Vector和一個Hashtable。并且在它們里面分別填充Mouse和Hamster對象(本章早些時候已定義了這些類;注意必須先編譯HamsterMaze.java和WorksAnyway.java,否則下面的程序不能編譯)。由于Enumeration隱藏了基層集合的結(jié)構(gòu),所以PrintData不知道或者不關(guān)心Enumeration來自于什么類型的集合:

//: Enumerators2.java
// Revisiting Enumerations
import java.util.*;
class PrintData {
 static void print(Enumeration e) {
  while(e.hasMoreElements())
   System.out.println(
    e.nextElement().toString());
 }
}
class Enumerators2 {
 public static void main(String[] args) {
  Vector v = new Vector();
  for(int i = 0; i < 5; i++)
   v.addElement(new Mouse(i));
  Hashtable h = new Hashtable();
  for(int i = 0; i < 5; i++)
   h.put(new Integer(i), new Hamster(i));
  System.out.println("Vector");
  PrintData.print(v.elements());
  System.out.println("Hashtable");
  PrintData.print(h.elements());
 }
}

注意PrintData.print()利用了這些集合中的對象屬于Object類這一事實(shí),所以它調(diào)用了toString()。但在解決自己的實(shí)際問題時,經(jīng)常都要保證自己的Enumeration穿越某種特定類型的集合。例如,可能要求集合中的所有元素都是一個Shape(幾何形狀),并含有draw()方法。若出現(xiàn)這種情況,必須從Enumeration.nextElement()返回的Object進(jìn)行下溯造型,以便產(chǎn)生一個Shape。

5、新集合

  新的集合庫考慮到了“容納自己對象”的問題,并將其分割成兩個明確的概念:

(1) 集合(Collection):一組單獨(dú)的元素,通常應(yīng)用了某種規(guī)則。在這里,一個List(列表)必須按特定的順序容納元素,而一個Set(集)不可包含任何重復(fù)的元素。相反,“包”(Bag)的概念未在新的集合庫中實(shí)現(xiàn),因為“列表”已提供了類似的功能。

(2) 映射(Map):一系列“鍵-值”對(這已在散列表身上得到了充分的體現(xiàn))。從表面看,這似乎應(yīng)該成為一個“鍵-值”對的“集合”,但假若試圖按那種方式實(shí)現(xiàn)它,就會發(fā)現(xiàn)實(shí)現(xiàn)過程相當(dāng)笨拙。這進(jìn)一步證明了應(yīng)該分離成單獨(dú)的概念。另一方面,可以方便地查看Map的某個部分。只需創(chuàng)建一個集合,然后用它表示那一部分即可。這樣一來,Map就可以返回自己鍵的一個Set、一個包含自己值的List或者包含自己“鍵-值”對的一個List。和數(shù)組相似,Map可方便擴(kuò)充到多個“維”,毋需涉及任何新概念。只需簡單地在一個Map里包含其他Map(后者又可以包含更多的Map,以此類推)。

在通讀了本章以后,相信大家會真正理解它實(shí)際只有三個集合組件:Map,List和Set。而且每個組件實(shí)際只有兩、三種實(shí)現(xiàn)方式(注釋⑥),而且通常都只有一種特別好的方式。只要看出了這一點(diǎn),集合就不會再令人生畏。

6、使用Collections

  boolean add(Object) *保證集合內(nèi)包含了自變量。如果它沒有添加自變量,就返回false(假)

boolean addAll(Collection) *添加自變量內(nèi)的所有元素。如果沒有添加元素,則返回true(真)

void clear() *刪除集合內(nèi)的所有元素

boolean contains(Object) 若集合包含自變量,就返回“真”

boolean containsAll(Collection) 若集合包含了自變量內(nèi)的所有元素,就返回“真”

boolean isEmpty() 若集合內(nèi)沒有元素,就返回“真”
Iterator iterator() 返回一個反復(fù)器,以用它遍歷集合的各元素

boolean remove(Object) *如自變量在集合里,就刪除那個元素的一個實(shí)例。如果已進(jìn)行了刪除,就返回“真”

boolean removeAll(Collection) *刪除自變量里的所有元素。如果已進(jìn)行了任何刪除,就返回“真”

boolean retainAll(Collection) *只保留包含在一個自變量里的元素(一個理論的“交集”)。如果已進(jìn)行了任何改變,就返回“真”int size() 返回集合內(nèi)的元素數(shù)量

Object[] toArray() 返回包含了集合內(nèi)所有元素的一個數(shù)組

7、使用Lists

  List(接口) 順序是List最重要的特性;它可保證元素按照規(guī)定的順序排列。List為Collection添加了大量方法,以便我們在List中部插入和刪除元素(只推薦對LinkedList這樣做)。List也會生成一個ListIterator(列表反復(fù)器),利用它可在一個列表里朝兩個方向遍歷,同時插入和刪除位于列表中部的元素(同樣地,只建議對LinkedList這樣做)

ArrayList* 由一個數(shù)組后推得到的List。作為一個常規(guī)用途的對象容器使用,用于替換原先的Vector。允許我們快速訪問元素,但在從列表中部插入和刪除元素時,速度卻嫌稍慢。一般只應(yīng)該用ListIterator對一個ArrayList進(jìn)行向前和向后遍歷,不要用它刪除和插入元素;與LinkedList相比,它的效率要低許多

LinkedList 提供優(yōu)化的順序訪問性能,同時可以高效率地在列表中部進(jìn)行插入和刪除操作。但在進(jìn)行隨機(jī)訪問時,速度卻相當(dāng)慢,此時應(yīng)換用ArrayList。也提供了addFirst(),addLast(),getFirst(),getLast(),removeFirst()以及removeLast()(未在任何接口或基礎(chǔ)類中定義),以便將其作為一個規(guī)格、隊列以及一個雙向隊列使用

8、使用Sets

  Set(接口) 添加到Set的每個元素都必須是獨(dú)一無二的;否則Set就不會添加重復(fù)的元素。添加到Set里的對象必須定義equals(),從而建立對象的唯一性。Set擁有與Collection完全相同的接口。一個Set不能保證自己可按任何特定的順序維持自己的元素

HashSet* 用于除非常小的以外的所有Set。對象也必須定義hashCode()

ArraySet 由一個數(shù)組后推得到的Set。面向非常小的Set設(shè)計,特別是那些需要頻繁創(chuàng)建和刪除的。對于小Set,與HashSet相比,ArraySet創(chuàng)建和反復(fù)所需付出的代價都要小得多。但隨著Set的增大,它的性能也會大打折扣。不需要HashCode()
TreeSet 由一個“紅黑樹”后推得到的順序Set。這樣一來,我們就可以從一個Set里提到一個順序集合

9、 使用Maps

  Map(接口) 維持“鍵-值”對應(yīng)關(guān)系(對),以便通過一個鍵查找相應(yīng)的值

HashMap* 基于一個散列表實(shí)現(xiàn)(用它代替Hashtable)。針對“鍵-值”對的插入和檢索,這種形式具有最穩(wěn)定的性能??赏ㄟ^構(gòu)建器對這一性能進(jìn)行調(diào)整,以便設(shè)置散列表的“能力”和“裝載因子”

ArrayMap 由一個ArrayList后推得到的Map。對反復(fù)的順序提供了精確的控制。面向非常小的Map設(shè)計,特別是那些需要經(jīng)常創(chuàng)建和刪除的。對于非常小的Map,創(chuàng)建和反復(fù)所付出的代價要比HashMap低得多。但在Map變大以后,性能也會相應(yīng)地大幅度降低

TreeMap 在一個“紅-黑”樹的基礎(chǔ)上實(shí)現(xiàn)。查看鍵或者“鍵-值”對時,它們會按固定的順序排列(取決于Comparable或Comparator,稍后即會講到)。TreeMap最大的好處就是我們得到的是已排好序的結(jié)果。TreeMap是含有subMap()方法的唯一一種Map,利用它可以返回樹的一部分

總結(jié)

以上就是本文關(guān)于Java編程思想對象的容納實(shí)例詳解的全部內(nèi)容,希望能對大家有所幫助,也希望大家對本站多多支持!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI