Java编程思想(5)

第15章 泛型

1 泛型的例子

class Automobile{}
public class ArrayApp<T> {	
	private T a;
	public ArrayApp(T a){
		this.a = a;
	}
	public void set(T a){
		this.a = a;
	}
	public T get(){
		return a;
	}
	public static void main(String[] args){
		ArrayApp<Automobile> h3 = new ArrayApp<Automobile>(new Automobile());
		Automobile a = h3.get();
	}
}

2 元组(tuple),是将一组对象直接打包存储于其中的一个单一对象。

public class TwoTuple<A,B> {
	public final A first;
	public final B second;
	public TwoTuple(A a,B b){
		first = a;
		second = b;
	}
	public String toString(){
		return "("+first+", "+second+")";
	}
}

3 一个堆栈类

public class TwoTuple<T> {
	private static class Node<U>{
		U item;
		Node<U> next;
		Node(){
			item = null;
			next = null;
		}
		Node(U item,Node<U> next){
			this.item = item;
			this.next = next;
		}
		boolean end(){
			return next == null && item == null;
		}
	}
	private Node<T> top = new Node<T>();   // 末端哨兵
	public void push(T item){
		top = new Node<T>(item,top);
	}
	public T pop(){
		T result = top.item;
		if(!top.end())
			top = top.next;
		return result;
	}
	public static void main(String[] args){
		TwoTuple<String> lss = new TwoTuple<String>();
		for(String s:"Phasers or stun!".split(" "))
			lss.push(s);
		String s;
		while((s=lss.pop())!=null)
			System.out.println(s);
	}
}

4 泛型也可以应用于接口,基本类型不能作为类型参数

interface Generator<T> { T next(); }
public class TwoTuple implements Generator<Integer> {
	private int count = 0;
	public Integer next(){
		return fib(count++);
	}
	private int fib(int n){
		if(n < 2) return 1;
		return fib(n-2)+fib(n-1);
	}
	public static void main(String[] args){
		TwoTuple gen = new TwoTuple();
		for(int i=0;i<18;i++)
			System.out.println(gen.next()+" ");
	}
}

5  泛型函数,泛型函数所在的类可以是泛型类,也可以不是泛型类。即是否拥有泛型函数,与其所在的类是否为泛型没有关系。

当使用泛型类时,必须在创建对象时指定类型参数的值,但使用泛型函数时,通常不必指明参数类型,编译器会使用类型参数推断。

public class TwoTuple {
	public <T> void f(T x){
		System.out.println(x.getClass().getName());
	}
	public static void main(String[] args){
		TwoTuple tt = new TwoTuple();
		tt.f("");
		tt.f(1);
		tt.f(1.0);
		tt.f(1.0f);
		tt.f('m');
		tt.f(tt);
	}
}

输出
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
c06.TwoTuple

6 有一条基本的指导原则:无论何时,只要能做到,尽量使用泛型函数static函数,无法访问泛型类的类型参数。所以如果static函数需要使用泛型能力,就必须使其成为泛型函数

7 类型参数推断值对赋值操作有效

public class TwoTuple {
	public static <K,V> Map<K,V> map(){
		return new HashMap<K,V>();
	}
	public static <T> List<T> list(){
		return new ArrayList<T>();
	}
	public static <T> LinkedList<T> lList(){
		return new LinkedList<T>();
	}
	public static <T> Set<T> set(){
		return new HashSet<T>();
	}
	public static <T> Queue<T> queue(){
		return new LinkedList<T>();
	}
	public static void f(Map<String,List<String>> s){}
	public static void main(String[] args){
		Map<String,List<String>> sls = TwoTuple.map();
		List<String> ls = TwoTuple.list();
		LinkedList<String> lls = TwoTuple.lList();
		Set<String> ss = TwoTuple.set();
		Queue<String> qs = TwoTuple.queue();
		f(TwoTuple.map());    // error 
		
	}
}

8 泛型函数中,可以用点操作符与函数名之间插入尖括号,把类型写入括号内来显式指明类型,不过这种语法很少做。如

f(TwoTuple.<String,List<String>>map()) ;

9  可变参数与泛型函数

public class TwoTuple {
	public static <T> List<T> makeList(T... args){
		List<T> result = new ArrayList<T>();
		for(T item:args){
			result.add(item);
		}
		return result;
	}
	public static void main(String[] args){
		List<String> ls = makeList("A");
		System.out.println(ls);
		ls = makeList("A","B","C");
		System.out.println(ls);
		ls = makeList("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""));
		System.out.println(ls);
	}
}

10 擦除的神秘之处

public class TwoTuple {
	public static <T> List<T> makeList(T... args){
		List<T> result = new ArrayList<T>();
		for(T item:args){
			result.add(item);
		}
		return result;
	}
	public static void main(String[] args){
		Class c1 = new ArrayList<String>().getClass();
		Class c2 = new ArrayList<Integer>().getClass();
		System.out.println(c1);  // class java.util.ArrayList
		System.out.println(c2);  // class java.util.ArrayList
	}
}
class Frob{}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION,MOMENTUM> {}
public class TwoTuple {	
	public static void main(String[] args){
		List<Frob> list = new ArrayList<Frob>();
		Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
		Quark<Fnorkle> quark = new Quark<Fnorkle>();
		Particle<Long,Double> p = new Particle<Long,Double>();
		System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
	}
}

输出
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]

在泛型代码内部,无法获得任何有关泛型参数类型的信息。Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了。因此List<String>和List<Integer>在运行时是相同的类型。

class HasF{
	public void f(){ System.out.println("HasF.f()");}
}
// 因为擦除,Java编译器无法将manipulator()必须能够在obj上调用f()这一需求映射到HasF拥有f()
class Manipulator<T>{
	private T obj;
	public Manipulator(T x){ obj = x;}
	public void manipulator(){ obj.f(); }   // error
} 
// 可以使用extends
class AnotherManipulator<T extends HasF>{
	private T obj;
	public AnotherManipulator(T x){ obj = x;}
	public void manipulator(){ obj.f(); }
} 

11 fruit其实是一个Apple数组引用。编译期,Fruit()和Orange()赋值给fruit是正常的,但运行时数组机制知道它处理的是Apple[ ],因此会抛出异常

class Fruit{}
class Apple extends Fruit{}
class Jonathan extends Apple{}
class Orange extends Fruit{}

public class TwoTuple {
	
	public static void main(String[] args){
		Fruit[] fruit = new Apple[10];
		fruit[0] = new Apple();
		fruit[1] = new Jonathan();
		try{
			fruit[0] = new Fruit();
		}catch(Exception e){
			System.out.println(e);
		}
		try{
			fruit[0] = new Orange();
		}catch(Exception e){
			System.out.println(e);
		}
	}
}

输出
java.lang.ArrayStoreException: c06.Fruit
java.lang.ArrayStoreException: c06.Orange

12 超类型通配符,方法是指定<? super MyClass>,甚至或者使用类型参数<? super T>。无界通配符<?>

13 Java泛型会出现的问题

  • 任何基本类型都不能作为类型参数
  • 一个类不能实现同一个泛型接口的两种变体,因为擦除原因,两种变体会成为相同的接口
interface Payable<T> {}
class Employee implements Payable<Employee>{}
class Hourly extends Employee implements Payable<Hourly>{}  // error
  • 使用带有泛型类型参数的转型或instanceof不会有任何效果
class FixedSizeStack<T>{
	private int index = 0;
	private Object[] storage;
	public FixedSizeStack(int size){
		storage = new Object[size];
	}
	public void push(T item){
		storage[index++] = item;
	}
	public T pop(){
		return (T)storage[--index];   // 转型
	}
}
public class TwoTuple {	
	public static final int SIZE = 10;
	public static void main(String[] args){
		FixedSizeStack<String> strings = new FixedSizeStack<String>(SIZE);
		for(String s:"A B C D E F G H I J".split(" "))
			strings.push(s);
		for(int i=0;i<SIZE;i++){
			String s = strings.pop();
			System.out.print(s+" ");
		}
	}
}
  • 重载,如下,但因为擦除原因重载方法将产生同样的类型签名。
public class TwoTuple<W,T> {	
	void f(List<T> v){}   // error
	void f(List<W> v){}   // error
}

第16章 数组

1 多维数组,可以通过使用花括号将每个向量分隔开

int[][] a = {
 {1,2,3},
 {4,5,6},
};
int[][][] b = new int[2][2][4];
System.out.println(Arrays.deepToString(a));

输出
[[1, 2, 3], [4, 5, 6]]

2 粗糙数组,每个向量具有任意的长度

Random rand = new Random(47);
int[][][] a = new int[rand.nextInt(7)][][];
for(int i=0;i<a.length;i++){
	a[i] = new int[rand.nextInt(5)][];
	for(int j=0;j<a[i].length;j++)
		a[i][j] = new int[rand.nextInt(5)];
}
System.out.println(Arrays.deepToString(a));
int[][] a = {{1},{1,2},{1,2,3},};

3 数组和泛型

Integer[] ints = {1,2,3,4,5};
Double[] doubles = {1.1,2.2,3.3,4.4,5.5};
Integer ints2[] = new ClassParameter<Integer>().f(ints);
Double doubles2[] = new ClassParameter<Double>().f(doubles);
ints2 = MethodParameter.f(ints);
doubles2 = MethodParameter.f(doubles);

4 Java标准类库Arrays有一个函数Arrays.fill(),只能用一个值填充各个位置。

int[] a = new int[6];
Arrays.fill(a, 1);

5 在java.util类库中可以找到Arrays类,它有一套用于数组的static实用方法,其中有六个基本方法:equals()用于比较两个数组是否相等(deepEquals()用于多维数组)fill()用于一个值填充整个数组sort()用于数组排序binarySearch()用于在已经排序的数组中查找元素toString()产生数组的String表示HashCode()产生数组的散列码

6 Java标准类库提供有static函数System.arraycopy(),用它复制数组比用for循环复制更快。arraycopy()的参数:源数组,表示从源数组中的什么位置开始复制的偏移量,目标数组,表示从目标数组的什么位置开始复制的偏移量,复制的元素个数。

int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.out.println("i = "+Arrays.toString(i));
System.out.println("j = "+Arrays.toString(j));
System.arraycopy(i, 0, j, 0, i.length);
System.out.println("j = "+Arrays.toString(j));
int[] k = new int[5];
Arrays.fill(k, 103);
System.arraycopy(i, 0, k, 0, k.length);
System.out.println("k = "+Arrays.toString(k));
Arrays.fill(k, 103);
System.arraycopy(k, 0, i, 0, k.length);
System.out.println("i = "+Arrays.toString(i));
Integer[] u = new Integer[10];
Integer[] v = new Integer[5];
Arrays.fill(u, new Integer(47));
Arrays.fill(v, new Integer(99));
System.out.println("u = "+Arrays.toString(u));
System.out.println("v = "+Arrays.toString(v));
System.arraycopy(v, 0, u, u.length/2,v.length);
System.out.println("u = "+Arrays.toString(u));

输出
i = [47, 47, 47, 47, 47, 47, 47]
j = [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]
j = [47, 47, 47, 47, 47, 47, 47, 99, 99, 99]
k = [47, 47, 47, 47, 47]
i = [103, 103, 103, 103, 103, 47, 47]
u = [47, 47, 47, 47, 47, 47, 47, 47, 47, 47]
v = [99, 99, 99, 99, 99]
u = [47, 47, 47, 47, 47, 99, 99, 99, 99, 99]

7 如果复制对象数组,只是复制对象引用,而不是对象本身的拷贝,则成为浅复制。

8 Arrays类提供了重载后的equals()方法,用来比较整个数组。数组相等的条件是元素个数必须相等,且对应位置的元素也相等。

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[4];
Arrays.fill(s1, "Hi");
String[] s2 = {new String("Hi"),new String("Hi"),new String("Hi"),new String("Hi"),};
System.out.println(Arrays.equals(s1, s2));

9 Java有两种方式来提供比较功能。第一种是实现java.lang.Comparable接口。另一种是编写自己的Comparator。

public class TwoTuple implements Comparable<TwoTuple> {
	int i;
	int j;
	private static int count = 1;
	public TwoTuple(int n1,int n2){
		i = n1;
		j = n2;
	}
	public String toString(){
		String result = "[i = "+i+", j = "+j + "]";
		if(count++ %3 == 0)
			result += "\n";
		return result;
	}
	@Override
	public int compareTo(TwoTuple o) {
		// TODO Auto-generated method stub
		return (i<o.i?-1:(i==o.i?0:1));
	}	
	private static Random r = new Random(47);
	public static void main(String[] args){
		TwoTuple[] t = new TwoTuple[12];
		for(int i=0;i<t.length;i++)
			t[i] = new TwoTuple(r.nextInt(100),r.nextInt(100));
		System.out.println("before sorting");
		System.out.println(Arrays.toString(t));
		Arrays.sort(t);
		System.out.println("after sorting");
		System.out.println(Arrays.toString(t));
	}
}
class CompTypeComparator implements Comparator<TwoTuple>{
	@Override
	public int compare(TwoTuple o1, TwoTuple o2) {
		// TODO Auto-generated method stub
		return (o1.i<o2.i?-1:(o1.i==o2.i?0:1));
	}
}

public class TwoTuple {
	int i;
	int j;
	private static int count = 1;
	public TwoTuple(int n1,int n2){
		i = n1;
		j = n2;
	}
	public String toString(){
		String result = "[i = "+i+", j = "+j + "]";
		if(count++ %3 == 0)
			result += "\n";
		return result;
	}	
	private static Random r = new Random(47);
	public static void main(String[] args){
		TwoTuple[] t = new TwoTuple[12];
		for(int i=0;i<t.length;i++)
			t[i] = new TwoTuple(r.nextInt(100),r.nextInt(100));
		System.out.println("before sorting");
		System.out.println(Arrays.toString(t));
		Arrays.sort(t,new CompTypeComparator());
		System.out.println("after sorting");
		System.out.println(Arrays.toString(t));
	}
}

第17章 容器深入研究

1 Java容器类库

2 填充容器

class StringAddress{
	private String s;
	public StringAddress(String s){
		this.s = s;
	}
	public String toString(){
		return super.toString()+ " "+ s;
	}
}
public class TwoTuple {	
	public static void main(String[] args){
		List<StringAddress> list = new ArrayList<StringAddress>(
				Collections.nCopies(4, new StringAddress("Hello")));  //创建4个元素的数组
		System.out.println(list);
		Collections.fill(list, new StringAddress("World"));
		System.out.println(list);
	}
}

3 Abstract类

public class TwoTuple extends AbstractList<Integer>{

	private int size;
	public TwoTuple(int size){
		this.size = size<0?0:size;
	}
	@Override
	public Integer get(int index) {
		// TODO Auto-generated method stub
		return Integer.valueOf(index);
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return size;
	}
	public static void main(String[] args){
		System.out.println(new TwoTuple(30));
	}
	
}

4 Map不是继承于Collection。如下是Collection的所有操作

Collection<String> c = new ArrayList<String>();
c.addAll(new ArrayList<String>(Arrays.asList("one","two")));
c.add("ten");
c.add("eleven");
System.out.println(c);
Object[] array = c.toArray();
String[] src = c.toArray(new String[0]);
System.out.println("Collections.max(c) = "+Collections.max(c));
System.out.println("Collections.min(c) = "+Collections.min(c));
Collection<String> s2 = new ArrayList<String>();
s2.addAll(new ArrayList<String>(Arrays.asList("one","two")));
c.addAll(s2);
System.out.println(c);
c.remove("eleven");
System.out.println(c);
c.remove("one");
System.out.println(c);
c.removeAll(s2);
System.out.println(c);
c.addAll(s2);
System.out.println(c);
String val = "one";
System.out.println("c.contains("+val+") = "+c.contains(val));
System.out.println("c.containsAll(s2) = "+c.containsAll(s2));
Collection<String> c3 = ((List<String>)c).subList(1, 2);
s2.retainAll(c3);
System.out.println(s2);;
s2.removeAll(c3);
System.out.println("s2.isEmpty() = "+s2.isEmpty());
c.clear();
System.out.println("after c.clear()"+c);

输出
[one, two, ten, eleven]
Collections.max(c) = two
Collections.min(c) = eleven
[one, two, ten, eleven, one, two]
[one, two, ten, one, two]
[two, ten, one, two]
[ten]
[ten, one, two]
c.contains(one) = true
c.containsAll(s2) = true
[one]
s2.isEmpty() = true
after c.clear()[]

5 因为Arrays.asList()会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作。任何会引起对底层数据结构的尺寸进行修改的方法都会产生一个UnsupportedOperationException异常,以表示对未获支持操作的调用。 

public class TwoTuple {
	static void test(String msg,List<String> list){
		System.out.println("--- "+msg+" ---");
		Collection<String> c = list;
		Collection<String> subList = list.subList(1,8);
		Collection<String> c2 = new ArrayList<String>(subList);
		try{
			c.retainAll(c2);
		}catch(Exception e){
			System.out.println("retainAll(): "+e);
		}
		try{
			c.clear();
		}catch(Exception e){
			System.out.println("clear(): "+e);
		}
		try{
			c.add("X");
		}catch(Exception e){
			System.out.println("add(): "+e);
		}
		try{
			c.addAll(c2);
		}catch(Exception e){
			System.out.println("addAll(): "+e);
		}
		try{
			c.remove("C");
		}catch(Exception e){
			System.out.println("remove(): "+e);
		}
		try{
			list.set(0, "X");
		}catch(Exception e){
			System.out.println("set(): "+e);
		}
	}
	public static void main(String[] args){
		List<String> list = Arrays.asList("A B C D E F G H I J K L".split(" "));
		test("Modifiable Copy",new ArrayList<String>(list));
		test("Arrays.asList()",list);
		test("unmodifiableList()",Collections.unmodifiableList(new ArrayList<String>(list)));
	}
}

6 Set和存储顺序

7 SortedSet,元素默认按大小排序

  • Object first() 返回容器中的第一个元素
  • Object last() 返回容器中的最后一个元素
  • SortedSet subSet(fromElement , toElement) 生成此Set的子集,范围 fromElement <= x < toElement
  • SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成
  • SortedSet tailSet(fromElement) 生成此Set的子集,由大于或等于fromElement的元素组成
SortedSet<String> sortedSet = new TreeSet<String>();
Collections.addAll(sortedSet,"one two three four five six seven eight".split(" "));
System.out.println(sortedSet);
String low = sortedSet.first();
String high = sortedSet.last();
System.out.println("low = "+low+" , high = "+high);
Iterator<String> it = sortedSet.iterator();
for(int i=0;i<=6;i++){
	if( i== 3) low = it.next();
	if(i == 6) high = it.next();
	else it.next();
}
System.out.println("low = "+low+" , high = "+high);
System.out.println(sortedSet.subSet(low, high));
System.out.println(sortedSet.headSet(high));
System.out.println(sortedSet.tailSet(low));

输出
[eight, five, four, one, seven, six, three, two]
low = eight , high = two
low = one , high = two
[one, seven, six, three]
[eight, five, four, one, seven, six, three]
[one, seven, six, three, two]

8 除了并发应用,Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue

9 标准的Java类库中包含了Map的几种基本实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap。

10 实现Map

public class TwoTuple<K,V> {
	private Object[][] pairs;
	private int index;
	public TwoTuple(int length){
		pairs = new Object[length][2];
	}
	public void put(K key,V value){
		if(index >= pairs.length)
			throw new ArrayIndexOutOfBoundsException();
		else{
			pairs[index++] = new Object[]{key,value};
		}
	}
	public V get(K key){
		for(int i=0;i<index;i++)
			if(key.equals(pairs[i][0]))
				return (V)pairs[i][1];
		return null;
	}
	public String toString(){
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<index;i++){
			sb.append(pairs[i][0].toString());
			sb.append(" : ");
			sb.append(pairs[i][1].toString());
			if(i < index -1)
				sb.append("\n");
		}
		return sb.toString();
	}
	
	public static void main(String[] args){
		TwoTuple<String,String> at = new TwoTuple<String,String>(6);
		at.put("sky", "blue");
		at.put("grass", "green");
		at.put("ocean", "dancing");
		at.put("tree", "tall");
		at.put("earth", "brown");
		at.put("sun","warm");
		try{
			at.put("extra", "object");
		}catch(ArrayIndexOutOfBoundsException e){
			System.out.println("Too many objects!");
		}
		System.out.println(at);
		System.out.println(at.get("ocean"));
	}
}

输出
Too many objects!
sky : blue
grass : green
ocean : dancing
tree : tall
earth : brown
sun : warm
dancing

注:当在get()中使用线性搜索时,执行速度比较慢,而这正是HashMap提高速度的地方。HashMap使用对象hashCode()进行快速搜索,即用散列码,来取代对键的缓慢搜索。散列码是相对唯一,用以代表对象的int值,它是通过将该对象的某些信息进行转而生成。hashCode()是根类Object的方法,所有Java对象都能产生散列码

11 下面是基本Map的实现,如果没有其他限制,应优先使用HashMap,因为它优化了速度。其他的Map都不如HashMap快

12 map的部分函数

System.out.println(map.getClass().getSimpleName());
map.putAll(new CountingMapData(25));
System.out.println(map.values());
System.out.println(map.containsKey(11));
System.out.println(map.get(11));
System.out.println(map.containsValue("ok"));
Integer key = map.keySet().iterator().next();
map.remove(key);
map.clear();
System.out.println(map.isEmpty());;
map.keySet().removeAll(map.keySet());

13 TreeMap确保键处于排序状态

  • T firstKey() 返回Map中的第一个键
  • T lastKey() 返回Map中的最后一个键
  • SortedMap subMap(fromKey , toKey) 生成此Map的子集,范围由fromKey(包含)到toKey(不包含)的键确定
  • SortedMap headMap(toKey) 生成此Map的子集,由键小于toKey的所有键值对组成
  • SortedMap tailMap(fromKey) 生成此Map的子集,由键大于或等于fromKey的所有键值对组成
TreeMap<Integer,String> sortedMap = new TreeMap<Integer,String>(new CountingMapData(10));
System.out.println(sortedMap);
Integer low = sortedMap.firstKey();
Integer high = sortedMap.lastKey();
System.out.println("low = "+low+" , high = "+high);
Iterator<Integer> it = sortedMap.keySet().iterator();
for(int i=0;i<=6;i++){
	if(i == 3) low = it.next();
	if(i == 6) high = it.next();
	else it.next();
}
System.out.println("low = "+low+" , high = "+high);
System.out.println(sortedMap.subMap(low, high));
System.out.println(sortedMap.headMap(high));
System.out.println(sortedMap.tailMap(low));

14 String有个特点,如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。所以new String("hello")生成的两个实例,虽然独立但hashMap()是同值。

String[] hellos = "Hello Hello".split(" ");
String k = new String("Hello");
String k1 = "Hello";
System.out.println(hellos[0].hashCode());   // 69609650
System.out.println(hellos[1].hashCode());   // 69609650
System.out.println(k.hashCode());    // 69609650
System.out.println(k1.hashCode());    // 69609650

15 散列码不必是独一无二,应该更关注生成速度,而不是唯一性。但是通过hashCode()和equals(),必须能够完全确定对象的身份。散列码的生成范围并不重要,只要是int即可。好的hashCode()应该产生分布均匀的散列码。

16 一种像样的hashCode()实现方法

public class TwoTuple {	
	private static List<String> created = new ArrayList<String>();
	private String s;
	private int id = 0;
	public TwoTuple(String str){
		s = str;
		created.add(s);
		for(String s2:created){
			if(s2.equals(s))
				id++;
		}
	}
	public String toString(){
		return "String: "+s+" id: "+id+" hashCode(): "+hashCode();
	}
	public int hashCode(){
		int result = 17;
		result = 37*result + s.hashCode();
		result = 37*result + id;
		return result;
	}
	public boolean equals(Object o){
		return o instanceof TwoTuple && s.equals(((TwoTuple)o).s) && id == ((TwoTuple)o).id;
	}
	public static void main(String[] args){
		Map<TwoTuple,Integer> map = new HashMap<TwoTuple,Integer>();
		TwoTuple[] cs = new TwoTuple[5];
		for(int i=0;i<cs.length;i++){
			cs[i] = new TwoTuple("hi");
			map.put(cs[i], i);
		}
		System.out.println(map);
		for(TwoTuple t:cs){
			System.out.println("Looking up "+t);
			System.out.println(map.get(t));
		}
	}
}

输出
{String: hi id: 1 hashCode(): 146447=0, String: hi id: 2 hashCode(): 146448=1, String: hi id: 3 hashCode(): 146449=2, String: hi id: 4 hashCode(): 146450=3, String: hi id: 5 hashCode(): 146451=4}
Looking up String: hi id: 1 hashCode(): 146447
0
Looking up String: hi id: 2 hashCode(): 146448
1
Looking up String: hi id: 3 hashCode(): 146449
2
Looking up String: hi id: 4 hashCode(): 146450
3
Looking up String: hi id: 5 hashCode(): 146451
4

17 java.lang.ref类库包含一组类,为垃圾回收提供更大的灵活性,有三个继承自抽象类Reference的类:SoftReference,WeakReference和PhantomReference。Softreference用以实现内存敏感的高速缓存,WeakReference是为实现“规范映射”而设计,它不妨碍垃圾回收器回收映射的键或值。PhantomReference用以调度回收前的清理工作。使用SoftReference和WeakReference时,可以选择是否要将它们放入ReferenceQueue,而PhantomReference只能依赖于ReferenceQueue。

18 许多老代码使用Java1.0/1.1的容器,新程序中最好别使用旧的容器。

  • Vector和Enumeration:Enumeration接口比Iterator小,只有两个方法:一个是boolean hasMoreElements()和另一个Object nextElement()。
  • Hashtable:Hashtable跟HashMap相似
  • Stack
  • BitSet
Vector<String> v = new Vector<String>(Counties.name(10));
Enumeration<String> e = v.elements();
while(e.hasMoreElements()){
	System.out.println(e.nextElement());
	e = Collections.enumeration(new ArrayList<String>());
}

第18章 Java I/O系统

1 File类仅代表一个特定文件的名称,又能代表一个目录下的一组文件名称。如果是文件集,我们可以对此集合调用list()方法,返回一个字符数组。

public class TwoTuple {		
	public static void main(String[] args){
		File path = new File(".");
		String[] list;
		if(args.length == 0){
			list = path.list();
		}else{
			list = path.list(new DirFilter(args[0]));
		}
		Arrays.sort(list,String.CASE_INSENSITIVE_ORDER);
		for(String dirItem:list)
			System.out.println(dirItem);
	}	
}
class DirFilter implements FilenameFilter{
	private Pattern pattern;
	public DirFilter(String regex){
		pattern = Pattern.compile(regex);
	}
	public boolean accept(File dir,String name){
		return pattern.matcher(name).matches();
	}
}

2 目录的检查和创建

public final class TwoTuple {		
	private static void usage(){
		System.err.println(
				"Usage:MakeDirectories path1 ...\n"+
		"Creates each path\n"+
						"Usage:MakeDirectories -d path1 ...\n"+
		"Deletes each path\n"+
						"Usage:MakeDirectories -r path1 ...\n"+
		"Renames from path1 to path2");
		System.exit(1);
	}
	private static void fileData(File f){
		System.out.println(
				"Absolute path: "+f.getAbsolutePath()
				+"\n Can read: "+f.canRead()
				+"\n Can write: "+f.canWrite()
				+"\n getName: "+f.getName()
				+"\n getParent: "+f.getParent()
				+"\n getPath: "+f.getPath()
				+"\n length: "+f.length()
				+"\n lastModified: "+f.lastModified());
		if(f.isFile())
			System.out.println("It's a file");
		else if(f.isDirectory())
			System.out.println("It's a directory");
	}
	public static void main(String[] args){
		if(args.length < 1) usage();
		if(args[0].equals("-r")){
			if(args.length != 3) usage();
			File old = new File(args[1]);
			File rname = new File(args[2]);
			old.renameTo(rname);
			fileData(old);
			fileData(rname);
			return;
		}
		int count = 0;
		boolean del = false;
		if(args[0].equals("-d")){
			count++;
			del = true;
		}
		count--;
		while(++count < args.length){
			File f = new File(args[count]);
			if(f.exists()){
				System.out.println(f+" exists");
				if(del){
					System.out.println("deleting ... "+f);
					f.delete();
				}
			}else{
				if(!del){
					f.mkdirs();
					System.out.println("created "+f);
				}
			}
			fileData(f);
		}
	}
}

3 任何自InputStream或Reader派生而来的类都含有名为read()函数,用于读取单个字节或字节数组。任何自OutputStream或Writer派生而来的类都含有名为write()函数,用于写单个字节或字节数组。

4 InputStream的作用是用来表示那些从不同数据源产生输入的类,数据源包括字节数组,String对象,文件,管道等。

5 OutputStream类型

6 FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流(InputStream)和输出流(OutputStream)的两个类

7 通过FilterInputStream从InputStream读取数据,DataInputStream允许我们读取不同的基本类型数据以及String对象。通过FilterOutputStream向OutputStream写入。DataOutputStream可以将各种基本类型数据以及String对象格式化输出到“流”中。

8 Reader和Writer

9 缓冲输入文件。为了提高速度,对文件进行缓冲,将所产生的引用传递给一个BufferedReader构造器。

public final class TwoTuple {
	public static String read(String filename) throws IOException{
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while((s = in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args) throws IOException{
		System.out.println(System.getProperty("user.dir"));
		System.out.println(read("./src/c06/TwoTuple.java"));
	}
}

10 从内存输入,从BufferedInputFile.read()读入的String结果被用来创建一个StringReader

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null)
	sb.append(s+"\n");
in.close();
StringReader tt = new StringReader(sb.toString());
int c;
while((c = tt.read())!= -1)
	System.out.println((char)c);

11 格式化的内存输入,可以使用DataInputStream。

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null)
	sb.append(s+"\n");
in.close();
try{
	DataInputStream tt = new DataInputStream(new ByteArrayInputStream(sb.toString().getBytes()));
	while(true)
		System.out.print((char)tt.readByte());
}catch(EOFException e){
	System.out.println("End of stream");
}

可以使用available()函数查看还有多少可供存取的字符。

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null)
	sb.append(s+"\n");
in.close();
DataInputStream tt = new DataInputStream(new ByteArrayInputStream(sb.toString().getBytes()));
while(tt.available() != 0)
	System.out.print((char)tt.readByte());

12 FileWriter对象可以向文件写入数据。首先创建一个与指定文件连接的FileWriter

public final class TwoTuple {
	static String file = "BasicFileOutput.out";
	public static String read(String filename) throws IOException{
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while((s = in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args) throws IOException{
		BufferedReader in = new BufferedReader(new StringReader(read("./src/c06/TwoTuple.java")));
		PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
		int lineCount = 1;
		String s;
		while((s = in.readLine()) != null)
			out.println(lineCount+++": "+s);
		out.close();
		System.out.print(read(file));
	}
}

13 文本文件输出的快捷方式

public final class TwoTuple {
	static String file = "BasicFileOutput.out2";
	public static String read(String filename) throws IOException{
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while((s = in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args) throws IOException{
		BufferedReader in = new BufferedReader(new StringReader(read("./src/c06/TwoTuple.java")));
		PrintWriter out = new PrintWriter(file);
		int lineCount = 1;
		String s;
		while((s = in.readLine()) != null)
			out.println(lineCount+++": "+s);
		out.close();
		System.out.print(read(file));
	}
}

14 存储和恢复数据,PrintWriter可以对数据进行格式化。但为了输出可供另一个流恢复数据。需要用DataOutputStream写入数据,并用DataInputStream恢复数据。注意DataOutputStream和DataInputStream是面向字节

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("./src/c06/Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That is pi");
out.writeDouble(1.14143);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("./src/c06/Data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());

15 使用RandomAccessFile读取随机访问文件 

public final class TwoTuple {
	static String file = "./src/c06/rtest.dat";
	static void display() throws IOException{
		RandomAccessFile rf = new RandomAccessFile(file,"r");
		for(int i=0;i<7;i++)
			System.out.println("Value "+i+": "+rf.readDouble());
		System.out.println(rf.readUTF());
		rf.close();
	}
	public static void main(String[] args) throws IOException{
		RandomAccessFile rf = new RandomAccessFile(file,"rw");
		for(int i=0;i<7;i++)
			rf.writeDouble(i*1.414);
		rf.writeUTF("The end of the file");
		rf.close();
		display();
		rf = new RandomAccessFile(file,"rw");
		rf.seek(5*8);    // 查找第5个双精度值,只需用5*8来产生查找位置
		rf.writeDouble(47.0001);
		rf.close();
		display();
	}
}

输出
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 7.069999999999999
Value 6: 8.484
The end of the file
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 47.0001
Value 6: 8.484
The end of the file

16 读取二进制文件

public final class TwoTuple {
	public static byte[] read(File bFile) throws IOException{
		BufferedInputStream bf = new BufferedInputStream(new FileInputStream(bFile));
		try{
			byte[] data = new byte[bf.available()];
			bf.read(data);
			return data;
		}finally{
			bf.close();
		}
	}
	public static byte[] read(String bFile) throws IOException{
		return read(new File(bFile).getAbsoluteFile());
	}
}

17 从标准输入中读取,Java提供了System.in,System.out和System.err。

BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = stdin.readLine()) != null && s.length() != 0)
	System.out.println(s);

18 把System.out转换成PrintWriter,System.out是一个PrintWriter,而PrintWriter是一个OutputStream。PrintWriter有一个可以接受OutputStream作为参数的构造器。因此,只要需要,就可以使用那个构造器把System.out转换成PrintWriter。

PrintWriter out = new PrintWriter(System.out,true);
out.println("Hello world");

19 标准I/O重定向

  • setIn(InputStream)
  • setOut(PrintStream)
  • setErr(PrintStream)
PrintStream console = System.out;
BufferedInputStream in = new BufferedInputStream(new FileInputStream("./src/c06/TwoTuple.java"));
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("./src/c06/test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = br.readLine()) != null)
	System.out.println(s);
out.close();
System.setOut(console);

20 压缩类,有CheckedInputStream,CheckedOutputStream,DeflaterOutputStream,ZipOutputStream,GZipOutputStream,InflaterInputStream,ZipInputStream和GZIPInputStream。

21 使用GZIP进行简单压缩,因此如果只想对单个数据流进行压缩可以使用这个

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("./src/c06/test.gz")));
System.out.println("Writing file");
int c;
while((c = in.read()) != -1)
	out.write(c);
in.close();
out.close();
System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream("./src/c06/test.gz"))));
String s;
while((s = in2.readLine()) != null)
	System.out.println(s);

22 使用Zip进行多文件保存。用Checksum类来计算和校验文件的校验和的方法。一共有两种Checksum类型:Adler32()(快一些)和CRC32(慢一些但更准确)。对于每个要加入压缩档案的文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。

FileOutputStream f = new FileOutputStream("./src/c06/test.zip");
CheckedOutputStream csum = new CheckedOutputStream(f,new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out = new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
for(String arg:args){
	System.out.println("Writing file "+arg);
	BufferedReader in = new BufferedReader(new FileReader(arg));
	zos.putNextEntry(new ZipEntry(arg));
	int c;
	while((c = in.read()) != -1)
		out.write(c);
	in.close();
	out.flush();
}
out.close();
System.out.println("Checksum: "+csum.getChecksum().getValue());
System.out.println("Reading file");
FileInputStream fi = new FileInputStream("./src/c06/test.zip");
CheckedInputStream  csumi = new CheckedInputStream(fi,new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null){
	System.out.println("Reading file "+ze);
	int x;
	while((x = bis.read()) != -1)
		System.out.println(x);
}
bis.close();
ZipFile zf = new ZipFile("./src/c06/test.zip");
Enumeration e = zf.entries();
while(e.hasMoreElements()){
	ZipEntry ze2 = (ZipEntry)e.nextElement();
	System.out.println("File: "+ze2);
}

23 Java档案文件,JDK自带的jar程序,命令行格式如下

jar [option] destination [manifest] inputfile(s)

option的选择字符

  • jar  cf  myJarFile.jar  *.class
  • jar  tf  myJarFile.jar  myManifestFile.mf  *.class
  • jar  tf  myJarFile.jar
  • jar cvf  myApp.jar  audio   classes   images

24 Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以以后将这个字节序列完全恢复为原来的对象。只要对象实现了Serialiable接口,对象序列化处理就很简单。

要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。只需调用writeObject()即可将对象序列化,并将其发送给OutputStream。

class Data implements Serializable{
	private int n;
	public Data(int n){
		this.n = n;
	}
	public String toString(){
		return Integer.toString(n);
	}
}

public class TwoTuple implements Serializable{	
	private static Random rand = new Random(47);
	private Data[] d = {
			new Data(rand.nextInt(10)),
			new Data(rand.nextInt(10)),
			new Data(rand.nextInt(10)),
	};
	private TwoTuple next;
	private char c;
	public TwoTuple(int i,char x){
		System.out.println("TwoTuple constructor: "+i);
		c = x;
		if(--i > 0)
			next = new TwoTuple(i,(char)(x+1));
	}
	public TwoTuple(){
		System.out.println("Default constructor");
	}
	public String toString(){
		StringBuilder sb = new StringBuilder(":");
		sb.append(c);
		sb.append("(");
		for(Data dat:d){
			sb.append(dat);
		}
		sb.append(")");
		if(next != null)
			sb.append(next);
		return sb.toString();
	}
	public static void main(String[] args) throws ClassNotFoundException,IOException{
		TwoTuple w = new TwoTuple(6,'a');
		System.out.println("w = "+w);
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./src/c06/worm.out"));
		out.writeObject("Worm storage\n");
		out.writeObject(w);
		out.close();
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("./src/c06/worm.out"));
		String s = (String)in.readObject();
		TwoTuple w2 = (TwoTuple)in.readObject();
		System.out.println(s+" w2 = "+w2);
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		ObjectOutputStream out2 = new ObjectOutputStream(bout);
		out2.writeObject("Worm storage\n");
		out2.writeObject(w);
		out2.flush();
		ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
		s = (String)in2.readObject();
		TwoTuple w3 = (TwoTuple)in2.readObject();
		System.out.println(s+" w3 = " +w3);

	}
}

25 可以使用transient关键字关闭序列化

public class TwoTuple implements Serializable{	
	
	private Date date = new Date();
	private String username;
	private transient String password;  // 不会进行序列化
	public TwoTuple(String name,String pwd){
		username = name;
		password = pwd;
	}
	public String toString(){
		return "login info: \n username: "+username+"\n date: "+date+"\n password: "+password;
 	}
	public static void main(String[] args) throws Exception{
		TwoTuple t = new TwoTuple("Hulk","123456");
		System.out.println(t);
		ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("./src/c06/login.out"));
		o.writeObject(t);
		o.close();
		TimeUnit.SECONDS.sleep(1);
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("./src/c06/login.out"));
		System.out.println("Recovering object at "+new Date());
		t = (TwoTuple)in.readObject();
		System.out.println("a = "+t);
	}
}

输出
login info: 
 username: Hulk
 date: Thu Oct 11 01:31:54 CST 2018
 password: 123456
Recovering object at Thu Oct 11 01:31:56 CST 2018
a = login info: 
 username: Hulk
 date: Thu Oct 11 01:31:54 CST 2018
 password: null

猜你喜欢

转载自blog.csdn.net/haima95/article/details/82916562