Java编程思想(11) 持有对象

【概述】

1、通常,程序需要根据运行时才知道的某些条件去创建新对象。在此之前,不知道所需对象的数量,甚至不知道确切的类型。
2、容器:Java实用类库提供了一套相当完整的容器类来解决这个问题。基本的类型包括:List、Set、Queue和Map,这些对象称为集合类。 用“容器”来称呼他们。
3、容器类都可以自动的调节自己的尺寸。编程时,可以将任意数量的对象放到容器中,而不需要担心放不下。


【泛型和类型安全的容器】

1、Java SE5之前,编译器允许向容器中插入不正确的类型。
2、以下例子会编译报警
    1)Apple和Orange类除了都是Object外没有共性。因为ArrayList中保存的是Object,因此通过Add可以放Apple,也可以放Orange。
    2)所以,通过get()取出来的值只是Object引用必须强制转化为Apple。

 //放Orange到Apple里的不良例子
import java.util.*;
class Apple{
    private static long counter;
    private final long id = counter++;
    public long id() {return id;}
}
class Orange{}
public class ApplesAndOrangeWitchoutGenerics{
    public static void main(String[] args){
        ArrayList  apples = new ArrayList();  //AppleArrayList Apples
        for(int i = 0; i < 3; i++)
        {
            apples.add(new Apples());
        }
        apples.add(new Orange()); //把一个桔子放进了苹果的类型的ArrayList中。
        
        for (int i = 0; i < apples.size(); i++)
        {
            ((apple)apples.get(i)).id();
            
        }
    }
}
 3、使用预定的泛型,可以避免这种个问题。
      定义保存Apple对象的ArrayList,申明形式为:ArrayList<Apple> 。其中尖括号括起来的是类型参数。 指定了容器实例可以保存的类型。

 
//使
import java.util.*;
class Apple{
    private static long counter;
    private final long id = counter++;
    public long id() {return id;}
}
class Orange{}
public class ApplesAndOrangeWitchoutGenerics{
    public static void main(String[] args){
        ArrayList<Apple>  apples = new ArrayList<Apple>();  //
        for(int i = 0; i < 3; i++)
        {
            apples.add(new Apples());
        }
        apples.add(new Orange()); //----
        
        for (int i = 0; i < apples.size(); i++)
        {
            //((apple)apples.get(i)).id();
            System.out.println(apples.get(i).id()); //--
            
        }
        for(Apple c:apples)
            System.out.print(c.id());   //for eachget
    }
}
 

4、虽然用泛型约束了类型,但不代表不能完全放其他类型,向上转型可以。
     即:基类的泛型可以放子类的

      
 
//使
impo java.util.*
class GrannySmit extends Apple{}   //
class Gala extends  Apple{}   //
class Fuji extends Apple{}   //
class Braeburn extends Apple{}   //
public class GenericsAndUpcasting{
    public static void main(String[] args){
        ArrayList<Apple> apples = new ArrayList<Apple>();
        apples.add(new GrannySmit());   //
        apples.add(new Gala());
        apples.add(new Fuji());
        apples.add(new  Braeburn());
        
        for(Apple c:apples)
        {
            System.out.println(c);
        }
    }
    
}
 GrannySmith@7d772f
Gala@2323
Fuji@1134de
Braeburn@74eaf
System.out.println,输出的是默认的Object的toString()方法产生的 ,打印类名+该对象的散列码的无符号16进制表示。

【基本概念】

1、容器类库可以分为两种概念
1)Collection:一个独立的元素序列。都有一条或者多条规则,比如:
     List必须按照插入的顺序保存元素
     Set不能有重复元素
     Queue按照排队规则来确定对象产生的顺序(通常与插入顺序相同)
2)Map:一组成对的“键值对”对象。允许使用键来查找值。ArrayList允许使用数字来查找值。
    映射表:允许使用另一个对象来查找某个对象,也叫做“关联数组”,或者被称为“字典

2、唯一需要指定所使用的精确类型的地方就是在创建时候。
List<Apple> apples = new ArrayList<Apple>();
LIst<Apple> apples = new LinkedList<Apple>();

这里的List可以看做接口。ArrayList和LInkedList都向上转型了。但是:他们所具备的特有方法将不能被使用。如果要使用,就不能向上转型

3、Collection概括了序列的概念。下面是个例子:

 
//Collection 
import java.util.*
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.println(i + ",");
}
0,1,2,3,4,5,6,7,8,9,
 

【添加一组元素】

1 、java.util的Arrays和Collection类都有一些可以添加一组元素(意思是多个)的方法
2、Arrays.asList()方法接受一个数组或者是一个用逗号分开的元素列表,将其转换成一个List对象。
     Collections.addAll()接受一个Collection对象,以及一个数组或者一个逗号分开的列表。(比上面多一种)

3、以下例子所表达的关键信息
1)Collection的泛型,在定义时,可以在其初始化时,用Arrays.asList()来产生输入。
2)也可以通过对象调用addAll(),以Arrays.asList()为参数,为其添加元素。当然,Arrays.asList()的参数可以是一个数组,或者一组逗号分开的元素列表
3)Collections.addAll(),运行要快得多。作为首选方法。第一个参数是collection,另一个参数可以是元素列表或数组。
4)Arrays.asList()和Collection.addAll()都可以使用可变参数列表,比较方便。
5)如果直接用Arrays.asList()的输出当做List,因为底层表示是数组,所以不能调整尺寸

 
package C_11;
import java.util.*;
public class AddinGroups {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //Arrays.asListArrayList
        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);
        Collections.addAll(collection,moreInts);
        
        List<Integer> list = Arrays.asList(16,17,18,19,20);
        list.set(1,9);
        //list.add(21); //resized
        
       for(Integer c:collection)
           System.out.println(c);
    }
}


4、Arrays.asList()方法有限制:它对所产生的List的类型做出最理想的假设,而不一定管赋值给它什么类型。
----这一段和感觉和书上讲的有点不一样。感觉书上想说的是,Arrays.asList参数应该和List<>的保持一致,不然编译不过。不过这里验证,没有出现这个问题。怎么回事(先放着吧,后面更纯熟后回来看)


import java.util.Arrays;
import java.util.List;

class Snow{}
class Powder extends Snow{}
class Light extends Powder{}
class Heavy extends Powder{}

class Crusty extends Snow{}
class Slush extends Snow{}


public class AsListInference {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Snow> snow1 = Arrays.asList(new Powder(),new Crusty(),new Slush());
		List<Snow> snow2 = Arrays.asList(new Light(),new Heavy());
		System.out.println("---snow1---");
		for(Snow a:snow1) {
			System.out.println(a);
		}
		
		System.out.println("---snow2---");
		for (Snow c : snow2) {
			System.out.println(c);
		}
		System.out.println("over");
		

	}

}
输出是:
---snow1---
C_11.Powder@1909752
C_11.Crusty@1f96302
C_11.Slush@14eac69
---snow2---
C_11.Light@a57993
C_11.Heavy@1b84c92
over

没毛病,没产生书上说的编译错误。

【容器的打印】


1、打印数组,需要用Arrays.toString(),但是容器不需要。直接把对象放进去,就打印出来了。
2、Collection每个槽中只能保存一个元素。
1)List:按顺序保存一组元素
2)Set:元素不能重复,好像重复添加也不报错。
3)Queue:一端进,另一端出

3、Map每个槽保存两个对象,即K-V

4、以下示例介绍了几个新的Collection
1)ArrayList是实现了基于动态数组的数据结构
2)LinkedList基于链表的数据结构。 LinkedList包含的操作多于ArrayList。
对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 
他们都是按照插入顺序保存对象。

3)HashSet实现了Set接口,它不允许集合中有重复的值,使用HashSet时,需要重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果没有重写这两个方法,将会使用这个方法的默认实现。
HashSet实现相对复杂,关注存续性能,不关注存储顺序

4)TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。如果关注存取顺序用这个。

5)LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

6)HashMap实现了Map接口,Map中不允许重复的键。查找最快。
5)TreeMap保存了对象的排列次序,而HashMap则不能
6)LinkedHshMap,是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap
Map访问数据结构是get(Key),put(Key,Value)。
Map可自动扩展尺寸。而且插入顺序和保存顺序不一样。
import java.util.*;



public class PrintingContainers {

	static Collection fill(Collection<String> strCollection) {
		strCollection.add("Hippo2");
		strCollection.add("Pig2");
		strCollection.add("Dog2");
		strCollection.add("Dog2");
		return strCollection;
	}
	
	static  Map fill(Map<String,String> map) {
		map.put("rat", "Kitty");
		map.put("cat", "Kitty");
		map.put("girl", "Lisha");
		map.put("girl", "Mili");
		return map;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		/*这样写是错误的,需要在创建实例时,集体化Collection
		Collection<String> collection1 = new Collection<String>();*/
		Collection<String> collection1 = new ArrayList<String>();
		collection1.add("Hippo1");
		collection1.add("seesaw1");
		System.out.println("---collection1---");
		/*print也用不了
		print(collection1);换成System.out.print可以了,书上的不好用。*/
		System.out.println(collection1);
		
		System.out.println("call Collection fill()--ArrayList");
		System.out.println(fill(new ArrayList<String>()));
		System.out.println("call Collection fill()--LinkedList");
		System.out.println(fill(new LinkedList<String>()));
		System.out.println("call Collection fill()--Hashset");
		System.out.println(fill(new HashSet<String>()));
		System.out.println("call Collection fill()--TreeSet");
		System.out.println(fill(new TreeSet<String>()));
		System.out.println("call Collection fill()--LinkedHashSet");
		System.out.println(fill(new LinkedHashSet<String>()));
		System.out.println("call Collection fill()--HashMap");
		System.out.println(fill(new HashMap<String,String>()));
		System.out.println("call Collection fill()--TreeMap");
		System.out.println(fill(new TreeMap<String,String>()));
		System.out.println("call Collection fill()--LinkedHashMap");
		System.out.println(fill(new LinkedHashMap<String,String>()));
			
	}

}
输出:
---collection1---
[Hippo1, seesaw1]
call Collection fill()--ArrayList
[Hippo2, Pig2, Dog2, Dog2]
call Collection fill()--LinkedList
[Hippo2, Pig2, Dog2, Dog2]
call Collection fill()--Hashset
[Pig2, Dog2, Hippo2]
call Collection fill()--TreeSet
[Dog2, Hippo2, Pig2]
call Collection fill()--LinkedHashSet
[Hippo2, Pig2, Dog2]
call Collection fill()--HashMap
{rat=Kitty, cat=Kitty, girl=Mili}
call Collection fill()--TreeMap
{cat=Kitty, girl=Mili, rat=Kitty}
call Collection fill()--LinkedHashMap
{rat=Kitty, cat=Kitty, girl=Mili}


上面的程序只是简单的一个演示。后面部分对每个容器开始详细介绍


【List】

1、两种类型的List
1)ArrayList,这是最基本的。可以理解成动态数组。随机访问快,插入删除慢
2)LinkedList,一看这个Linked,就可以想到链表。插入修改方便,随机访问慢。而且特性集比较大
2、这部分代码就不写了。主要的思想还比较好理解
1)List是可以resize的。在调用add()时,会扩大,新的元素会加入。remove()删除元素,删除不存在的元素返回false,否则是返回ture。可以是用元素,也可以是用下标,如:remove(p),remove(2)。
2)contains()函数,用来判断某个元素是否在数组中。返回true或false
3)get(下标)获取元素,indexof(元素)取得下标,不在返回-1。
4)用equals方法来判断是否相同。每个元素都被定义为唯一的对象。所以即使列表中有两个Cymric,再创建一个Cymric,并传递给indexOf(),还是找不到。同样remove()也一样。所以equals()很关键。
5)add(下标,元素),在指定位置插入对象。
6)subList(x,y)截取一段。[x,y)。对subList的修改,都会反映到初始列表,反之亦然。
7)Collections.sort()对List排序。containsAll()判断一个对象序列是否在一个大的序列里面,而且与顺序无关。
8)retainAll()是交集,注意判断还是一句equals()。
9)removeAll,删除参数中多个元素。removeAll(sub)。
10)addAll是加入多个元素。addAll(sub)。
11)isEmpty(),判断是否为空
12)clear(),清空所有元素
13)toArray转化为一个数组。


【迭代器】

1、从这个例子看它要解决的问题
  原来对着List编码的,后来发现能把相同代码用于Set,会更好。为避免重新写通用代码,使用迭代器。

2、迭代器:是一个对象,它的工作是遍历并选择序列中的对象,而程序员不必知道或关系该序列底层的结构 。
  迭代器也被称为轻量级对象,创建它的代价小

3、Java的Iterator只能单向移动,只能用来:
1)用next()方法获取下一个元素
2)hasNext()检查序列中是否还有元素。
3)用remove()将迭代器新返回的元素删除

import java.util.*;

public class SimpleIteration {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//没有导入书上的例子,Pets类,现在不能运行,主要看语法。
		List<Pet> pets  = Pets.arrayList(12);
		//地名义Iterator
		Iterator<Pet> it= pets.iterator();
		
		//循环判断是否有元素
		while(it.hasNext())
		{
			pet p = it.next();
			System.out.println(p.id()+":"+p+" ");
		}
		
		//因为单向移动,所以又得放到头上
		it=pets.iterator();  
		for(int i=0;i<6;i++) {
			//移动到下一个
			it.next();
			//删除
			it.remove();
		}
		
	}

}
4、只要是容器的Iterator即可,
如:
ArrayList<Pet> pets = XX;
LinkedList<Pet> pestLL = XX;
HashSet<Pet> petsHS = X;
TresSet<Pet> petsTS = XX;

注意,这里有List,也有Set
调用函数时,可以

display(pets.iterator());
display(pestLL.iterator());
display(petsHS.iterator());
display(petsTS.iterator());

这里分别传递了不同容器类型的iterator();

而在具体的处理函数里面,无所谓什么容器类型。

public static void display(Iterator<Pet> it){

   在这里面就可以用Iterator的函数,如hasNext(),next()进行操作。
}

从而实现了display()方法与具体的序列类型分离



5、再看一个叫做ListIterator

1)不像Iterator只能单向移动,ListIterator可以双向移动。
2)有hasPrevious()和hasNext()两个方法判断前后
3)有set()可以替换它访问过的最后一个元素
4)listIterator(),创建指向List开始处的ListIterator,也可以带参数n从特定位置开始,listIterator(n)。
5)最重要,它只能用于List,Set不可以了。

【LinkedList】

前面提了很多与ArrayList的对比了,这里介绍一下它其他一些方法
1、getFirst()与element()一样,返回第一个元素。如果List为空,返回NoSuchElementException。peek()方法功能类似,只是在List为空时,返回null。
2、removeFirst()与remove()完全一样。移除并返回列表的头。在列表为空时返回NoSuchElementException。同样,也有个poo(),此时返回null。

3、addFisrt()/add()/addLast()都是将元素插入到列表的尾(端)部
4、removeLast()移除最后一个元素
5、在LinkedList基础上添加一系列方法,可以成为Queue的实现。说明这两个关系很紧密。


【Stack】

1、后进先出
2、LinkedList能直接实现栈的所有功能,可以直接作为栈使用。
这个实现还是很巧妙的,值得围观。
package C_11;


import java.util.LinkedList;
//基于LinkedList实现一个Stack的类
public class Stack<T> {
	private LinkedList<T> storage = new LinkedList<T>();
	
	public void push(T v) {
		System.out.println("C_11 push()"+v);
		storage.addFirst(v); //这里添加到第一个,意味着先进去的,被推到后面
		}
    public T peek() {return storage.getFirst();}
    public T pop() {
    	System.out.println("C_11 pop"+ storage.peek()); //取排在第一个(用push()最后加入的)
    	return storage.removeFirst(); //移除第一个,则后进先出
    	}
    public boolean empty() {return storage.isEmpty();}
    public String toString() {return storage.toString();}
}
public class Stack<T>:这个<T>告诉编译器,这将是一个参数化类型。在类被使用时,会将实际的类型替换该参数。

在这个申明中:定义一个可以持有T类型的对象的Stack。Stack内部用LinkedList实现,LinkedList也被告知是T类型。

3、实际在java.util中,已经包含了一个Stack的类。书上说认为java.util.Stack实现不好。所以,如果想使用自己创建的这个,使用时要指定完整包名。在我的例子里面,就是C_11。下面代码分别用了自己定义的Stack,还有java.util.Stack。

package C_11;

import java.io.PushbackInputStream;

public class StackCollision {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//指定包名C_11,用自己定义的Stack
		C_11.Stack<String> stack01 = new C_11.Stack<String>();
		
		for(String s:"Test the new Stack class.".split(" ")) {
			stack01.push(s);
			System.out.println(stack01);
		}
		
		while(!stack01.empty())	{
			stack01.pop();
			
		}
		
		System.out.println("---------Test the java.util.Stack.-------push()------");
		//用java.util.Stack
		java.util.Stack<String> stack02=new java.util.Stack<String>();
		
		for(String s1:"Test the java.util.Stack.".split(" ")) {
			stack02.push(s1);
			System.out.println(s1);
		}
		
		System.out.println("---------Test the java.util.Stack.-------pop()------");
		while(!stack02.empty()) {
			
			System.out.println(stack02.pop());
			
		}
		
	}
}
执行效果如下:

C_11 push()Test
[Test]
C_11 push()the
[the, Test]
C_11 push()new
[new, the, Test]
C_11 push()Stack
[Stack, new, the, Test]
C_11 push()class.
[class., Stack, new, the, Test]
C_11 popclass.
C_11 popStack
C_11 popnew
C_11 popthe
C_11 popTest
---------Test the java.util.Stack.-------push()------
Test
the
java.util.Stack.
---------Test the java.util.Stack.-------pop()------
java.util.Stack.
the
Test



【Set】

1、Set里面无重复。最常用的场景是,判断某个对象是否在Set中。查找是Set最重要的操作。通常会选择HashSet,查询快。

package C_11;

import java.util.*;

public class SetOfInteger {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//构建一个随机数发生器,20是种子
		Random rand = new Random(47);
		Integer int01;
		/*
		for(int j = 0; j<100;j++)
		{
			System.out.println(rand.nextInt(30));
		}*/
		Set<Integer> set01=new HashSet<Integer>();
		for(int i = 0; i <100; i++) {
			int01 = rand.nextInt(20);
			//从打印来看,可以确认,数字还是随机的,
			System.out.print(int01+",");
			//用随机数发生器,取[0,30)的数字,并放到Set中。虽然循环了1000次,但实际插入的并不多,因为有重复的
			set01.add(int01);
		}
		System.out.println("");
		System.out.println("Print Set ----");
		//单最后打印set时,是按照顺序来排的
		System.out.println(set01);

	}

}
输出

18,15,13,1,1,9,8,0,2,7,8,8,11,9,9,18,18,1,0,18,16,0,11,2,4,3,6,15,10,2,14,4,10,6,2,16,13,4,4,10,3,1,18,12,6,9,5,12,6,16,7,8,14,15,5,14,16,2,16,7,17,14,2,17,4,2,0,18,1,19,12,5,14,9,2,9,18,18,3,13,6,14,10,4,17,13,1,8,8,8,7,19,15,2,10,17,19,11,13,17,
Print Set ----
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]


2、但在打印HashSet时,是排序(升序)了的。和书上说的为了性能,不排序的说法不一样。查了一下,这只是巧合,不能依赖这里的有序。知乎上有分析:https://www.zhihu.com/question/28414001/answer/40734236

大概就是:插入HashSet的是Integer,其hashCode()实现就返回int值本身。所以在对象hashCode这一步引入了巧合的“按大小排序”。
这关系到Set的实现细节和Hash算法的问题。


3、如果确实想获取可信的排序形式Set,请使用TreeSet。大概差不多。这里就不看代码了

4、用contains()来判断Set中是否包含某个元素。
5、TreeSet()缺省排序是把大、小写字母区分开的。比如,会排成
Aa B a b这样的。
如果不想用大小写区分开,只是看字母顺序,则需要向TreeSet的构造器传入String.CASE_INSENTIVE_ORDER比较器。

6、对比代码如下

package C_11;

import java.util.Set;
import java.util.TreeSet;

public class UniqueWordAlphabetic {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Set<String> strSet = new TreeSet<String>();
	//	Set<String> strSet02 = new TreeSet<String>();
		
		for(String c:"Input some words and some Aa df ec L0 b de Ka KC".split(" "))
			strSet.add(c);
		//这个的输出是按字母顺序,但是大小写字母区分的
		System.out.println(strSet);
		
	    System.out.println("-------------using String.CASE_INSENSITIVE_ORDER----------");
	    Set<String> strSet02 = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
	    for(String d:"Input some words and some Aa df ec L0 b de Ka KC".split(" "))
	       	strSet02.add(d);
	    
	    System.out.println(strSet02);
	}
}
可以看出,输出上的差异:
[Aa, Input, KC, Ka, L0, and, b, de, df, ec, some, words]
-------------using String.CASE_INSENSITIVE_ORDER----------
[Aa, and, b, de, df, ec, Input, Ka, KC, L0, some, words]

【Map】


1、可以看做是一个KV的数据库一样的效果。
2、get(Key)获得对应的Value;put(K,V)写Value
3、Map以及其他的Collection,很容易扩展到多维,从而组织强大的数据结构。如:
 HashMap<Integer,Integer>:K和V都是整数
 HashMap<String,Pet>:K是字符串,V是对象
 Hashmap<Person, List<Pet>>:Key是对象,V是对象列表

4、书上有一句这样写:
   public static Map<Person, List<? extends Pet>>
  ?是java泛型中的通配符,它代表java中的某一个类,那么<? extends T>就代表类型T的某个子类,<? super T>就代表类型T的某个父类

5、下面是一个Key为对象,V为对象列表的例子,表示一个人下面有多个宠物
  
package C_11;

import java.util.*;

//构建自己的人员的类
class myOwnPerson{
	private String name;
	public  myOwnPerson(String strName) {
		name = new String(strName);
		
	}
	
	//如果不定义类的toString,则会按照格式显示,如:C_11.myOwnPerson@1909752
	public String toString() {
		return name;
	}
}

//构建宠物的类
class myOwnPet{
	private String petKind;
	private String petName;
	


	public myOwnPet(String valPetKind, String valPetName)
	{
		petKind = new String(valPetKind);
		petName = new String(valPetName);
	}
	
	
	public String GetPetKind()
	{
		return this.petKind;
	}
	
	public String GetPetName() {
		return this.petName;
	}
	//@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "PetKind:"+petKind+","+"PetName:"+petName;
		//return super.toString();
	}
	
}
public class MapOfList {

	//顶一个一个HasMap,用来保存相关人和宠物列表的关系
	public static Map<myOwnPerson,List<myOwnPet>> MapOfListExample = new HashMap<myOwnPerson,List<myOwnPet>>();
	
	//添加关联关系
	static {
		MapOfListExample.put(new myOwnPerson("Person1"),Arrays.asList(new myOwnPet("Cat","pet1"),new myOwnPet("Cat", "pet2")));
		MapOfListExample.put(new myOwnPerson("Person2"),Arrays.asList(new myOwnPet("Cat","pet1"),new myOwnPet("Dog","Pet2")));
	//哈可以添加很多
		
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//调用keySet()显示所有的Key
		System.out.println("MapOfListExample.KeySet = "+ MapOfListExample.keySet());
		//调用values(),显示所有的value
		System.out.println("MapOfListExample.valueSet = "+ MapOfListExample.values() );
		
		//循环,在每个key下面,把相应的value列出来。
	    for(myOwnPerson p:MapOfListExample.keySet()) {
	    	System.out.println(p+ " has:");
	    	for(myOwnPet pet:MapOfListExample.get(p)) {
	    		System.out.println(pet+",");
	    	} 
	    }

   }
}
MapOfListExample.KeySet = [Person1, Person2]
MapOfListExample.valueSet = [[PetKind:Cat,PetName:pet1, PetKind:Cat,PetName:pet2], [PetKind:Cat,PetName:pet1, PetKind:Dog,PetName:Pet2]]
Person1 has:
PetKind:Cat,PetName:pet1,
PetKind:Cat,PetName:pet2,
Person2 has:
PetKind:Cat,PetName:pet1,
PetKind:Dog,PetName:Pet2,


注意在定义类时,要有toString,才能打印所需信息,否则就是打印类地址。

通常不用对象做Key,不好通过containsKey()来判断,因为判断的是一个对象是否相同,除非重新定义比较方法()。



【Queue】
1、队列特点是FIFO,通常用作可靠的将对象从程序某个区域传输到另一个区域。
2、FIFO可以用LinkedList来实现。看来LinkedList很万能啊。
3、说是LinkedList实现了Queue的接口,所以创建Queue,实际还是用LinkedList来定义。
Queue<Integer> Queue01 = new LinkedList<Integer>();
4、向Queue添加数据,如果插入不成功,返回false。
Queue01.offer(rand.nextInt());
5、peek()和element()都在不移除的情况下返回队头。只是队列为空时,peek()返回null,而element()抛出NoSuchElementException异常。
6、pool()和remove()移除并返回队头。队列为空时,pool()返回null,而remove()抛出NoSuchElementException异常。

这里有个小技巧
for(char c:"dfienadf".toCharArray())
类似于前面的在字符串上用".split()",这里把前面的一串字符转化程单个的字母数组。



【PriorityQueue】
1、FIFO是先进先出,这里谈一下优先级队列。
2、当在PriorityQueue上调用offer()方法插入一个对象时,这个对象在队列中会被排序。可以通过修改Comparator来修改排队规则。
3、以下举例PriorityQueue为Integer、String、Character,并可以通过offer()函数插入,也可以用List、Set等容器来进行填充。

package C_11;

import java.util.*;

public class PriorityQueueDemo {

	private static void PrintQueueDemo(Queue queue) {
		while(queue.peek()!=null)
		{
			System.out.print(queue.remove()+",");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PriorityQueue<Integer> priorityQueue01 = new PriorityQueue<Integer>();
		
		int num = 0;
		
		Random rand01 = new Random(67);
		System.out.println("Insert to priorityQueue01:");
		for(int i = 0; i < 10; i++) {
			//打印一下插入数据
			num = rand01.nextInt(i+10);
			System.out.print(num+",");
			priorityQueue01.offer(num);
		}
		System.out.println();
		//打印是按顺序的,从小到大
		System.out.println("Print value from priorityQueue01:");
		PrintQueueDemo(priorityQueue01);
		
		//---------------------------------------------------//
		//用Integer List给priorityQueue赋值
		List<Integer> ints = Arrays.asList(25,44,12,56,323,22,68,43,14,6,1,77); 
		System.out.println("List :"+"25,44,12,56,323,22,68,43,14,6,1,77");
		
		priorityQueue01 = new PriorityQueue<Integer>(ints);
		System.out.println("Print value from priorityQueue01:");
		PrintQueueDemo(priorityQueue01);
		
		
		//用字符串给priorityQueue赋值,字符串排序
		System.out.println("List: ADCE< KDFEL HF EDA  DLFKD , dfdf");
		PriorityQueue<String> priorityQueue02 = new PriorityQueue<String>();
		for(String s: "ADCE< KDFEL HF EDA  DLFKD , dfdf".split(" "))
				priorityQueue02.offer(s);
		PrintQueueDemo(priorityQueue02);
		
		//可以用List来初始化Queue
		String abc = "dfdk zfdk afd dfkdf d";
		List<String> abcStr = Arrays.asList(abc.split(" "));
		PriorityQueue priorityQueue03 = new PriorityQueue<String>(abcStr);
		PrintQueueDemo(priorityQueue03);
		
		//也可以用Set来初始化
		Set<Character> charSet = new HashSet<Character>();
		for(char c:abc.toCharArray())
			charSet.add(c);
		
		PriorityQueue<Character> priorityQueue04 = new PriorityQueue<Character>(charSet);
		PrintQueueDemo(priorityQueue04);
		

	}

}
输出:


Insert to priorityQueue01:
9,9,2,9,10,8,13,0,10,4,
Print value from priorityQueue01:
0,2,4,8,9,9,9,10,10,13,
List :25,44,12,56,323,22,68,43,14,6,1,77
Print value from priorityQueue01:
1,6,12,14,22,25,43,44,56,68,77,323,
List: ADCE< KDFEL HF EDA  DLFKD , dfdf
,,,ADCE<,DLFKD,EDA,HF,KDFEL,dfdf,
afd,d,dfdk,dfkdf,zfdk,
 ,a,d,f,k,z,
4、Integer、String和Character这些类已经内建了自然排序。如果其他类,需要提供自己的COmparator才行。

【Collection和Iterator】

1、Collection是描述所有序列容器的共性根接口。
2、使用接口描述的一个理由是能创建更通用的代码。通过针对接口而非具体实现来编写代码,代码可以用于更多的对象类型。
3、一个方法可以接受一个Collection,那么该方法就可以应用于任何实现Collection的类
4、实现Collection还需要提供Iterator()方法。

如:
	public static void display(Collection<myOwnPet> pets)
	{
	    for(myOwnPet p:pets)
	    {
	        System.out.println(p);
	        
	    }
	}
这里用Collection做入参。则可以支撑输入其他各种具体容器序列,如:

package C_11;

import java.util.*;
import C_11.myOwnPet;//从MapOfList.java中导入

public class InterfaceVsIterator {

	public static void display(Collection<myOwnPet> pets)
	{
	    for(myOwnPet p:pets)
	    {
	        System.out.println(p);
	        
	    }
	    System.out.println();
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<myOwnPet> petList = Arrays.asList(new myOwnPet("k0", "bb"),new myOwnPet("k1", "cc"),new myOwnPet("k2", "cc"));
		Set<myOwnPet> petSet = new HashSet<myOwnPet>();
		petSet.add(new myOwnPet("k3","ad"));
		petSet.add(new myOwnPet("k4","ade"));
		//List和Set都可以作为display的输入。
		display(petList);
		display(petSet);

	}

}


5、C++中是用迭代器来实现的。Java里面也可以有一种用法。作为参数,调用底层的Iterator。如:display还可以输入一个Iterator。只要容器实现了Iterator这里就可以调用,达到逐个访问的效果。
如:
public static void display(Iterator<myOwnPet>,it)
{
    while(it.hasNext())
    {
        myOwnPet p = it.next();
       System.out.println(p);
    }
}

调用时可以是:
display(petList.iterator());
display(petSet.iterator());


6、上面是两种方法,认为用Collection更好一些。
7、但是,当实现的不是一个Collection类时,想让它用Collection接口,就会比较麻烦。用Iterator会相对好一些。
 如果继承一个持有Pet对象的类来创建一个Collection类的实现,则必须实现所有的Collection方法。可以通过继承AbstractCollection实现,但还是需要实现Iterator()和size()。因为AbstractCollection内其他方法会调用。

public class CollectionSequence extends AbstractCollection{
   private Pet[] ptes = Ptes.createArray(8);
    public Iterator<Pet> iterator(){
        public boolean hasNext()
        {
        ......
        }
        public Pet next(){return pets[index++];}
        
    }
    public int size(){return pets.length;}
}

这里简单示意一下如何用一个持有Pet对象的类,构建一个Collection。通过扩展AbstractCollection实现。重点是重载实现了Iterator()和size()。

如果不能继承AbstractCollection(比如已经继承了其他类),则需要实现Collection的所有方法。


【foreach与迭代器】

1、foreach语法可以用于任何Collection对象。
2、核心是实现了Iterable的类。它内部有Iterator()。

 



基本上这章就结束了。容器应用很广,后续再找机会实践的时候再进一步琢磨吧。

------2018.1.10 23:20  深圳






内容为:学习笔记,加一写补充。总体来源,都还是书上的《Java编程思想》
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页