您的位置:首页 > 编程语言 > Java开发

Tinking in java 琐碎知识点之集合(容器)

2014-04-23 17:46 316 查看
1、Iterator必须依附于Collection接口的对象,Iterator本身不提供盛装对象的能力。Collectionname.iterator()方法获得Iterator接口实现类的对象

2、除了使用Iterator遍历Collection集合里的元素,也可以使用foreach循环(注意类型转换)

3、Set接口不允许包含重复元素(根据equals方法判断),而Collection没有此限制。Set接口的两个实现类HashSet是使用的哈希算法决定元素的存储位置,而treeSet使用的是红黑树算法。

4、Object类中定义了hashCode和equals方法,equals方法按照内存地址比较两个对象是否相等,hashCode方法返回值是根据内存地址计算出来的,所以同一个对象hashCode一样。HashSet是通过equas方法和hashCode方法判断两个元素是否相等的,将一个对象放入HashSet中时,HashSet会调用对象的的hashCode方法获得哈希码,再根据这个哈希码计算出对象在集合中存放的位置。因此如果重写了对应类的equals方法则也应该重写hashCode方法,保证如果2个对象通过equals比较相等那这两个对象的hashCode也应该一样,否则HashSet无法正常工作。
  (即使两个对象equals方法返回true而hashCode不同也可以添加成功,使HashSet无法正常工作)

5、TreeSet集合是SortSet接口的唯一实现,TreeSet集合里的元素处于排序状态。试图把一个对象添加进TreeSet时,则该对象的类必须实现comparable接口,因为TreeSet会调用集合的元素的compareTo(Object obj)方法来比较元素之间的大小关系。
TreeSet自然排序是根据元素的大小以升序排序,也可以使用定制排序,即不用将类实现Comparable接口了,直接在创建TreeSet集合对象时提供一个Comparator对象作为TreeSet构造器参数,由该Comparator对象实现集合元素的排序逻辑。

6、compareTo(Object obj)方法返回一个整数值,如果obj1.compareTo(obj2)返回0,则表明这两个对象相等。返回一个正整数则表明obj1>obj2,如果返回一个负整数,则obj1<obj2

7、集合里的元素总是引用,但习惯上把被引用的对象称为集合元素

8、在实际应用中TreeSet集合里的对象属性被改变了不会重新排序,也就是说排序只是在元素加进集合时。所以最适合用treeSet排序的是不可变类。

9、为了保持Set规则的一致性,即Set集合中的元素总是不重复的,我们应该尽量保证两个对象通过equals方法和compareTo方法比较的结果一致(还要重写hashCode方法)。

10、关于Set的小结
    HashSet性能比TreeSet好(特别是添加、查询操作),只有当要一个保持排序的Set时才应该使用TreeSet
    EnumSet性能虽好,但是它只能保存同一个枚举类的枚举值作为集合参数

11、List接口
    ArrayList和Vector都是List的实现类(Vector老了,虽然它是线程安全的都是也不推荐使用),ArrayList不是线程安全的,得手动控制,
    Vector有个Stack子类,可以模拟栈这种数据结构。LinkedList也是一个List的实现类,它是基于链表实现的List

12、LinkedList和ArrayList、Vector的实现机制完全不同,ArrayList、Vector内部以数组的形式来保存集合中的元素,因此随机访问集合元素有较好的性能,而LinkedList内部以链表的形式来保存集合元素,随机访问性能差,但是插入删除元素时性能出色。

13、HashMap和Hashtable都是Map接口的实现类,他们的关系类似于ArrayList和Vector,Hashtable老了,用起来也不方便。另外Hashtable是线程安全的,HashMap是线程不安全的。(即使要使用线程安全的实现类也不必使用HashTable和Vector,有Collections工具)

14、为了在HashMap和Hashtable中存储、获取对象,用作key的对象必须实现hashCode和equals方法。HashMap和Hashtable判断两个key相等的标准是:两个key 通过equals方法返回true,通过hashCode方法返回值相等。而它们判断两个value是否相等则只要通过equals对象返回true即可。
(当重写了一个类的equals方法后,最好也重写一下hashCode方法,以保证一致性)
public class MapTest {
public static void main(String[] args) {
HashMap<PerId,Person> hm=new HashMap<PerId,Person>();
hm.put(new PerId(0001), new Person("周鹏程1",25));
hm.put(new PerId(0002), new Person("周鹏程2",25));
hm.put(new PerId(0003), new Person("周鹏程3",26));
hm.put(new PerId(0004), new Person("周鹏程",27));
hm.put(new PerId(0005), new Person("周鹏程",27));
System.out.println(hm.containsKey(new PerId(0004)));
System.out.println(hm.containsValue( new Person("周鹏程1",25)));
System.out.println(hm.remove(new PerId(0001)));
for(Object o:hm.keySet()){
System.out.println(hm.get(o).name);
}
}

}

class Person{
String name;
int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public boolean equals(Object obj){
if(obj==this){
return true;
}
if(obj!=null&&obj.getClass()==Person.class){
Person p=(Person)obj;
if(p.age==this.age&&p.name==this.name){
return true;
}
}
return false;
}
}

class PerId{
int id;
public PerId(int id){
this.id=id;
}
public boolean equals(Object obj){
if(obj==this){
return true;
}
if(obj!=null&&obj.getClass()==PerId.class){
PerId p=(PerId)obj;
if(p.id==this.id){
return true;
}
}
return false;
}
//假如没有重写hashCode方法将会出现问题
public int hashCode(){
return id;
}
}


15、遍历Map中全部key-value对:调用Map对象的keySet方法返回全部key组成的Set对象,再通过遍历Set的元素(get方法获得key对应的    value)就可以遍历Map中所有的key-value对。

16、HashMap、Hashtable保存key的方式和HashSet保存集合元素的方式一至,所以HashMap、Hashtable对key的要求与HashSet对集合元素的要求相同。如果重写了该类的equals方法则应该也重写hashCode方法。

17、与HashSet类似的是,尽量不要使用可变对象作为HashMap、Hashtable的key,如果使用了可变对象,则尽量不要在程序中修改作为key的可变对象。

18、Hashtable有一个实用的子类Properties类,在处理属性文件时特别方便。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value写入属性文件,也可以把属性文件中的属性名、属性值加载到Map对象中。
例程:

import java.io.*;
import java.util.*;
public class TestProperties  {
public static void main(String[] args)throws Exception {
Properties props=new Properties();
props.setProperty("username", "zpc");
props.setProperty("password", "123456");
//将Properties中的属性保存到a.ini对象中
props.store(new FileOutputStream("a.ini"), "comment line(属性文件)");
Properties props2=new Properties();
props2.setProperty("gender", "male");
props2.load(new FileInputStream("a.ini"));
System.out.println(props2);
}
}
//输出:{password=123456, gender=male, username=zpc}


19、正如Set接口派生出SortedSet子接口,SortedSet接口有一个TreeSet实现类,Map接口有一个SortedMap子接口,SortedMap也有一个TreeMap实现类。与TreeSet类似,TreeMap也是基于红黑树对TreeMap中的所有key进行排序,从而保证TreeMap中所有key-value对处于有序状态。TreeMap也有自然排序(key所在的类实现Comparable接口、所有key应该是同一个类的对象)和客户化(创建TreeMap时传入一个Comparator对象,该对象负责实现排序逻辑)排序两种方式。

20、IdentityHashMap与HashMap基本相似,只是在处理两个key是否相等时不一样:只有当两个key严格相等(key1==key2)时才会认为两个key相等
public static void main(String[] args) {
IdentityHashMap ihm=new IdentityHashMap();
ihm.put(new String("语文"),89);
ihm.put(new String("语文"),78);
ihm.put("java",89);
ihm.put("java",89);
System.out.println(ihm);
}//“java”字符串是字符串直接量,放在缓存,而new的两个对象用==比较不等


21、操作集合工具类Collections提供了一些方法对集合元素进行排序、查询和修改操作,还提供了将集合对象设置成不可变、对集合对象实现同步控制等方法。

//showHand游戏
import java.util.*;
class ArrayUtils {
/**
* 定义一个工具方法,工具方法从字符串数组中找到对应的字符串元素的位置
*
* @param array
*            搜索的数组
* @param target
*            搜索的字符串
* @return 目标字符串出现的位置,-1表明找不到
*/
public static int search(String[] array, String target) {
for (int i = 0; i < array.length; i++) {
if (array[i] != null && array[i].equals(target)) {
return i;
}
}
return -1;
}
}

public class ShowHand {
private final int PLAY_NUM = 4;// 限定玩家数量
private String[] types = { "\4 ", "\5", "\3", "\6" };//特殊字符,会在控制台打印出方块、草花、红心和黑桃
private String[] values = { "2", "3", "4", "5", "6", "7", "8", "9", "10",
"J", "Q", "K", "A" };
// cards存放牌
private List<String> cards = new LinkedList<String>();
// 用一个数组存放玩家
private String[] players = new String[PLAY_NUM];
// 每一个玩家都用一个List存放其获得的牌
private List<String>[] playersCards = new List[PLAY_NUM];

// 初始化,放入扑克牌
public void initCards() {
for (int i = 0; i < types.length; i++) {
for (int j = 0; j < values.length; j++) {
cards.add(types[i] + values[j]);
}
}
// 调用集合工具类的方法随机排列
Collections.shuffle(cards);
}

// 初始化玩家,为每个玩家分配用户名
public void initPlayer(String... names) {
if (names.length < 2 || names.length > PLAY_NUM) {
System.out.println("输入玩家数量不对!");
return;
} else {
// 初始化用户
for (int i = 0; i < names.length; i++) {
players[i] = names[i];
}
}
}

// 初始化保存玩家牌的List
public void initPlayerCards() {
for (int i = 0; i < players.length; i++) {
if (players[i] != null && !players.equals("")) {
playersCards[i] = new LinkedList<String>();
}
}
}

// 写一个输出全部扑克牌的方法
public void showAllCards() {
for (String card : cards) {
System.out.println(card);
}
}

// 给玩家派牌的方法(指定一个最先派牌的玩家)
public void deliverCard(String first) {
int firstPos = ArrayUtils.search(players, first);
//依次给位于指定玩家之后、之前的每个玩家派牌
for(int i=firstPos;i<players.length;i++){
if(players[i]!=null){
playersCards[i].add(cards.get(0));
cards.remove(0);
}
}
//为之前的每个玩家派牌
for(int i=0;i<firstPos;i++){
if(players[i]!=null){
playersCards[i].add(cards.get(0));
cards.remove(0);
}
}
}
//输出玩家手中的牌
public void showPlayerCards(){
System.out.print("当前牌况:");
for(int i=0;i<players.length;i++){
if(players[i]!=null){
System.out.println("\n"+players[i]+":");
for(String card:playersCards[i]){
System.out.print (card+"\t");
}
}

}
System.out.print("\n");
}

public static void main(String[] args) {
ShowHand sh = new ShowHand();
sh.initCards();
sh.initPlayer("鸟鹏", "鸟朱");
sh.initPlayerCards();
sh.showAllCards();
System.out.println("*****************");
//第一次从玩家鸟鹏开始派牌
sh.deliverCard("鸟鹏");
sh.showPlayerCards();
//第二次从玩家鸟朱开始派牌
sh.deliverCard("鸟朱");
sh.showPlayerCards();
}
}


22、泛型:JDK1.5支持的泛型很大程度上是为了让集合能记住其元素的类型,这样会带来许多好处(Java泛型可以保证如果程序在编译的时候没有警告,运行时就不会产生ClassCastException异常,集合中默认存放的都是object对象,取出来还得强制转型,使用泛型就不要了)。

//一个泛型的案例
public class Apple<T> {
private T info;
public Apple() {
}
public Apple(T info){
this.info=info;
}
public T getInfo(){
return info;
}
public static void main(String[] args){
//因为传给T形参的是String实际类型,所以构造器的参数只能是String
Apple<String> apple1=new Apple<String>("苹果");
System.out.println(apple1.getInfo());
System.out.println(apple1.getClass());
Apple<Double> apple2=new Apple<Double>(12.9);
System.out.println(apple2.getInfo());
System.out.println(apple2.getClass());
}
}
//输出
//苹果
//12.9


泛型数组(Java允许创建无上限的通配符泛型数组):

List<?>[] lsa= new ArrayList<?>[10];
Object[] oa=(Object[])lsa;
List<Integer> li=new ArrayList<Integer>();
li.add(new Integer(7));
oa[1]=li;
System.out.println("lsa.getClass():"+lsa.getClass());
System.out.println("lsa[1].get(0).getClass():"+lsa[1].get(0).getClass());
//String s=(String)lsa[1].get(0);//List的get方法
Object target=lsa[1].get(0);
if(target instanceof String){
String s=(String)target;
}


23、注意:在使用带了泛型声明的接口、父类之后,在使用这些接口或父类时不能再包含类型形参(方法中的形参只有当定义方法时才使用数据形参,当调用(使用)方法时必须传入实际的数据;与此类似的是:类、接口中的类型参数只有在定义类、接口时才可以使用类型参数,当使用类、接口时应该为类型参数传入实际的类型)。

如上例继承Apple类时可以有两种写法

public class A extends Apple<String>{}

或者直接不写<T>即:public class A extends Apple{}

数组和泛型有所不同,假设Son是Father的一个子类型(子类或者子接口),那么Son[]依然是Father[]的一个子类型,但是C<Son>并不是C<Father>的一个子类型。

24、使用类型通配符

(设定类型通配符的上限)比如List<? extends Shape>表示所有Shape泛型List的父类,List集合的元素要么是Shape类型的要么是Shape的子类型都行

(设定类型形参的上限)也可以在定义类时设定类型上限:public class Apple<T extends Number>。

也可以使用泛型方法:

修饰符 <T,S> 返回值类型 方法名(形参列表)

{

  //方法体

}
例程:
abstract class Shape{
public abstract void draw(Canvas c);
}

class Circle extends Shape{
@Override
public void draw(Canvas c) {
System.out.println("在画布"+c+"画一个圆");
}
}

class Rectangule extends Shape{
@Override
public void draw(Canvas c) {
System.out.println("在画布"+c+"画一个矩形");
}
}

public class Canvas {
//同时在画布上绘制多个形状
//使用被限制的泛型通配符
public void drawAll(List<? extends Shape> shapes){
for(Shape s:shapes){
s.draw(this);
}
}

public static void main(String[] args) {
List<Circle> circleList=new ArrayList<Circle>();
List<Rectangule> rectangule=new ArrayList<Rectangule>();
circleList.add(new Circle());
rectangule.add(new Rectangule());
Canvas c=new Canvas();
c.drawAll(circleList);
c.drawAll(rectangule);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: