820 likes | 926 Views
第 4 讲 Java 对象容器与异常处理. 一、 Java 容器. 内容. 对象保持 再论数组 容器概述 List , Set , Map 小结. 1. 对象保持. 在程序中,经常需要动态产生一些对象 你无法知道要产生多少对象 你无法事先知道这些对象的类型 对象持有方式 内置的数组方式 容器类. 2. 再论数组 (Arrays). 提供了存储及随机访问一系列对象的 最有效率 的方法 由于线性存储,所以访问效率高 容量固定 提供边界检查 定义数组时,需要定义特定类型 概念 array object 是本身对象
E N D
内容 • 对象保持 • 再论数组 • 容器概述 • List,Set,Map • 小结
1. 对象保持 • 在程序中,经常需要动态产生一些对象 • 你无法知道要产生多少对象 • 你无法事先知道这些对象的类型 • 对象持有方式 • 内置的数组方式 • 容器类
2. 再论数组(Arrays) • 提供了存储及随机访问一系列对象的最有效率的方法 • 由于线性存储,所以访问效率高 • 容量固定 • 提供边界检查 • 定义数组时,需要定义特定类型 • 概念 • array object是本身对象 • objects array”由对象形成的数组” • primitive array”基本型别元素构成的数组”
//: arrays/ArrayOptions.java // Initialization & re-assignment of arrays. import java.util.*; import static net.mindview.util.Print.*; public class ArrayOptions { public static void main(String[] args) { // Arrays of objects: BerylliumSphere[] a; // Local uninitialized variable BerylliumSphere[] b = new BerylliumSphere[5]; // The references inside the array are // automatically initialized to null: print("b: " + Arrays.toString(b)); BerylliumSphere[] c = new BerylliumSphere[4]; for(int i = 0; i < c.length; i++) if(c[i] == null) // Can test for null reference c[i] = new BerylliumSphere(); // Aggregate initialization: BerylliumSphere[] d = { new BerylliumSphere(), new BerylliumSphere(), new BerylliumSphere() }; // Dynamic aggregate initialization: a = new BerylliumSphere[]{ new BerylliumSphere(), new BerylliumSphere(), };} // (Trailing comma is optional in both cases) print("a.length = " + a.length); print("b.length = " + b.length); print("c.length = " + c.length); print("d.length = " + d.length); a = d; print("a.length = " + a.length); // Arrays of primitives: int[] e; // Null reference int[] f = new int[5]; // The primitives inside the array are // automatically initialized to zero: print("f: " + Arrays.toString(f)); int[] g = new int[4]; for(int i = 0; i < g.length; i++) g[i] = i*i; int[] h = { 11, 47, 93 }; // Compile error: variable e not initialized: //!print("e.length = " + e.length); print("f.length = " + f.length); print("g.length = " + g.length); print("h.length = " + h.length); e = h; print("e.length = " + e.length); e = new int[]{ 1, 2 }; print("e.length = " + e.length); }
返回数组 publicstatic String[] flavorSet(int n) { if(n > FLAVORS.length) thrownew IllegalArgumentException("Set too big"); String[] results = new String[n]; boolean[] picked = newboolean[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; } publicstaticvoid main(String[] args) { for(int i = 0; i < 7; i++) System.out.println(Arrays.toString(flavorSet(3))); }} //: arrays/IceCream.java // Returning arrays from methods. import java.util.*; publicclass IceCream { privatestatic Random rand = new Random(47); staticfinal String[] FLAVORS = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge", "Rum Raisin", "Praline Cream", "Mud Pie" };
数组的几个功能 Arrays.fill(a1, true); print("a1 = " + Arrays.toString(a1)); Arrays.fill(a2, (byte)11); print("a2 = " + Arrays.toString(a2)); Arrays.fill(a3, 'x'); print("a3 = " + Arrays.toString(a3)); Arrays.fill(a4, (short)17); print("a4 = " + Arrays.toString(a4)); Arrays.fill(a5, 19); print("a5 = " + Arrays.toString(a5)); Arrays.fill(a6, 23); print("a6 = " + Arrays.toString(a6)); Arrays.fill(a7, 29); print("a7 = " + Arrays.toString(a7)); Arrays.fill(a8, 47); print("a8 = " + Arrays.toString(a8)); Arrays.fill(a9, "Hello"); print("a9 = " + Arrays.toString(a9)); // Manipulating ranges: Arrays.fill(a9, 3, 5, "World"); print("a9 = " + Arrays.toString(a9)); } } • 充填 //: arrays/FillingArrays.java // Using Arrays.fill() import java.util.*; import static net.mindview.util.Print.*; public class FillingArrays { public static void main(String[] args) { int size = 6; boolean[] a1 = new boolean[size]; byte[] a2 = new byte[size]; char[] a3 = new char[size]; short[] a4 = new short[size]; int[] a5 = new int[size]; long[] a6 = new long[size]; float[] a7 = new float[size]; double[] a8 = new double[size]; String[] a9 = new String[size];
复制 int[] i = new int[7]; int[] j = new int[10]; Arrays.fill(i, 47); Arrays.fill(j, 99); System.arraycopy(i, 0, j, 0, i.length);
//: arrays/ComparingArrays.java // Using Arrays.equals() import java.util.*; import static net.mindview.util.Print.*; 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); print(Arrays.equals(a1, a2)); a2[3] = 11; print(Arrays.equals(a1, a2)); String[] s1 = new String[4]; Arrays.fill(s1, "Hi"); String[] s2 = { new String("Hi"), new String("Hi"), new String("Hi"), new String("Hi") }; print(Arrays.equals(s1, s2)); } } • 比较
元素的比较 • 方法1:实现java.lang.Comparable interface,中的函数compareTo() • 方法2:撰写一个class,令它实现Comparator interface, 它包含两个函数:compare()和equals() Strategy 模式:将变化的部分与不变的部分分离,变化的部分放在单独的类(Strategy对象)中
排序 • 使用内置的sorting函数,你可以针对任何primitives array进行排序,也可以针对任何objects array进行排序(只要那些对象实现了Comparable或者拥有相关之Comparator)
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)); } } ///:~ 必须实现的接口 匿名内隐类 package com.bruceeckel.util; publicinterface Generator { Object next(); } ///:~
3. 容器概述 • 当你撰写程序时不知道究竟需要多少对象时,使用容器 • 两种类型的容器 • Collection:一组各自独立的元素,包括List, Set • Map: 成对的key-value对象
3.1 容器放入对象 • ArrayList不进行类型检查 importjava.util.*; class Apple { privatestaticlongcounter; privatefinallong id = counter++; publiclong id() { return id; } } class Orange {} publicclassApplesAndOrangesWithoutGenerics { @SuppressWarnings("unchecked") publicstaticvoid main(String[] args) { ArrayList apples = newArrayList(); for(inti = 0; i < 3; i++) apples.add(new Apple()); // Not prevented from adding an Orange to apples: apples.add(new Orange()); for(inti = 0; i < apples.size(); i++) ((Apple)apples.get(i)).id(); // Orange is detected only at run time } } /* (Execute to see output) *///:~
//: holding/ApplesAndOrangesWithGenerics.java import java.util.*; publicclass ApplesAndOrangesWithGenerics { publicstaticvoid main(String[] args) { ArrayList<Apple> apples = new ArrayList<Apple>(); for(int i = 0; i < 3; i++) apples.add(new Apple()); // Compile-time error: // apples.add(new Orange()); for(int i = 0; i < apples.size(); i++) System.out.println(apples.get(i).id()); // Using foreach: for(Apple c : apples) System.out.println(c.id()); } }
3.3容器的缺点 • 当对象放入容器时,型别信息就被损失 • 容器中保存的都是引用,需要自己维护其类型
3.4迭代器(Iterators) • 容器需要提供一些公共的功能 • 取元素 • 删除元素 • 查询元素 • 提供一个公共对象,执行基本功能,从而可以不管类型:Iterator
Java中的Iterator功能比较简单,并且只能单向移动:Java中的Iterator功能比较简单,并且只能单向移动: • 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。 • 使用next()获得序列中的下一个元素。 • 使用hasNext()检查序列中是否还有元素。 • 使用remove()将迭代器新返回的元素删除。 • Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
4 容器分类 • 短虚线方块代表interfaces,实线方块代表一般的类,虚线箭头表示实现某个接口的类,实线箭头表示某个类可以产生所指的那个类的对象
4.1 Iterator与ListIterator • ListIterator有add()方法,可以向List中添加对象,而Iterator不能 • ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。 • ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。 • 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
4.2 Collection的功能 • Set和List具备Collection的功能,Map并不继承Collection • boolean add(Object) • boolean addAll(Collection) (“Optional.”) • void clear( ) (“Optional.”) • boolean contains(Object) • boolean containsAll(Collection) • boolean isEmpty( ) • Iterator iterator( ) • boolean remove(Object)(“Optional.”) • boolean removeAll(Collection) (“Optional.”) • boolean retainAll(Collection)(“Optional.”) • int size( ) • Object[] toArray( ) • Object[] toArray(Object[] a) 可选:防止在设计中出现接口爆炸的情况,允许特例下,那些方法没有实现,但是绝大多数情形下是实现的 只保留相交的元素
4.3 List • List(interface):次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。 • ArrayList:由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。 • LinkedList:对顺序访问进行了优化,向List中间插入与删除的开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
Set(interface):存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。Set(interface):存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。 • HashSet:为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。 • TreeSet:保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。 • LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。 • HashSet采用散列函数对元素进行排序,这是专门为快速查询而设计的;TreeSet采用红黑树的数据结构进行排序元素;LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。
4.4 Map • 如果需要依据某些条件来在一串对象中进行选择 • Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。
Map 接口和方法: • 覆盖的方法, 我们Object 的这两个方法覆盖,以正确比较 Map 对象的等价性 • equals(Object o):比较指定对象与此 Map 的等价性 • hashCode():返回此 Map 的哈希码 • Map 构建,Map 定义了几个用于插入和删除元素的变换方法 • clear():从 Map 中删除所有映射 • remove(Object key):从 Map 中删除键和关联的值 • put(Object key, Object value):将指定值与指定键相关联 • putAll(Map t):将指定 Map 中的所有映射复制到此 map
查看 Map • 迭代 Map 中的元素不存在直接了当的方法。 如果要查询某个 Map 以了解其哪些元素满足特定查询,或如果要迭代其所有元素(无论原因如何),则您首先需要获取该 Map 的“视图”。 有三种可能的视图 • entrySet():返回 Map 中所包含映射的 Set 视图。 Set 中的每个元素都是一个 Map.Entry 对象,可以使用 getKey() 和 getValue() 方法(还有一个 setValue() 方法)访问后者的键元素和值元素 • keySet():返回 Map 中所包含键的 Set 视图。 删除 Set 中的元素还将删除 Map 中相应的映射(键和值) • values():返回 map 中所包含值的 Collection 视图。 删除 Collection 中的元素还将删除 Map 中相应的映射(键和值)
访问元素:Map 通常适合按键(而非按值)进行访问,这些方法检索有关 Map 内容的信息但不更改 Map 内容。 • get(Object key) 返回与指定键关联的值 • containsKey(Object key)如果 Map 包含指定键的映射,则返回 true • containsValue(Object value)如果此 Map 将一个或多个键映射到指定值,则返回 true • isEmpty()如果 Map 不包含键-值映射,则返回 true • size()返回 Map 中的键-值映射的数目
关于hashCode • 如果不覆写所使用的key的hashcode()和Equals(),那么HashSet或HashMap都无法正确处理
引用类 • Java 2 平台引入了 java.lang.ref 包,其中包括的类可以让您引用对象,而不将它们留在内存中。这些类还提供了与垃圾收集器(garbage collector)之间有限的交互。 • 引用类的主要功能就是能够引用仍可以被垃圾收集器回收的对象。
在引入引用类之前,我们只能使用强引用(strong reference)。举例来说,下面一行代码显示的就是强引用 obj : • Object obj = new Object(); obj 这个引用将引用堆中存储的一个对象。只要 obj 引用还存在,垃圾收集器就永远不会释放用来容纳该对象的存储空间。 • 当 obj 超出范围或被显式地指定为 null 时,垃圾收集器就认为没有对这个对象的其它引用,也就可以收集它了。 • 强引用是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
软引用(Soft Reference) • 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。 • 只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 • 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference) • 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(PhantomReference) • “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 • 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
WeakHashMap • 在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据
小结 • 1、容器类和Array的区别、择取 * 容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至数列某位置。 * 一旦将对象置入容器内,便损失了该对象的型别信息。 • 2、 * 在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList();Vector总是比ArrayList慢,所以要尽量避免使用。 * 在各种Sets中,HashSet通常优于HashTree(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。HashTree存在的唯一理由:能够维护其内元素的排序状态。 * 在各种Maps中HashMap用于快速查找。 * 当元素个数固定,用Array,因为Array效率是最高的。 • 结论:最常用的是ArrayList,HashSet,HashMap,Array。
1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。2、Set和Collection拥有一模一样的接口。3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。2、Set和Collection拥有一模一样的接口。3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。 • 5、Map用 put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。HashMap会利用对象的hashCode来快速找到key。 *hashing哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。 我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。 发生碰撞时,让array指向多个values。即,数组每个位置上又生成一个梿表。 • 6、Map中元素,可以将key序列、value序列单独抽取出来。使用keySet()抽取key序列,将map中的所有keys生成一个Set。使用values()抽取value序列,将map中的所有values生成一个Collection。 • 为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。
内容 • 异常的概念 • 异常的分类 • 捕获异常 • 声明异常 • 抛出异常 • 创造自己的异常
1 异常的概念 • 什么是异常? 异常实际上是程序中错误导致中断了正常的指令流的一种事件. • 没有处理错误的程序: read-file { openTheFile; determine its size; allocate that much memory; closeTheFile; }
以常规方法处理错误 openFiles; if (theFilesOpen) { determine the lenth of the file; if (gotTheFileLength){ allocate that much memory; if (gotEnoughMemory) { read the file into memory; if (readFailed) errorCode=-1; else errorCode=-2; }else errorCode=-3; }else errorCode=-4 ; }else errorCode=-5;
观察前面的程序你会发现大部分精力花在出错处理上了.观察前面的程序你会发现大部分精力花在出错处理上了. • 只把能够想到的错误考虑到,对以外的情况无法处理 • 程序可读性差 • 出错返回信息量太少
用异常的形式处理错误 read-File; { try { openTheFile; determine its size; allocate that much memory; closeTheFile; }catch(fileopenFailed) { dosomething; } catch(sizeDetermineFailed) {dosomething;} catch(memoryAllocateFailed){ dosomething;} catch(readFailed){ dosomething;} catch(fileCloseFailed) { dosomething; } }