Java 复习篇四

从天津转站北京,爬到中关村到直立行走
Java 复习篇四
Collection集合
集合是指一个对象容纳了多个对象,这个集合对象主要用来管理维护一系列相似的对象。
数组就是一种对象。
java.util.*定义了一系列的接口和类,告诉我们用什么类NEW出一个对象,可以进行超越数组的操作。
(注:JAVA1.5对JAVA1.4的最大改进就是增加了对范型的支持)
集合框架接口的分类:(分collection接口 和 map接口)
            Collection接口                         Map接口


List接口         Set接口                        SortedMap接口


                    SortedSet接口
JAVA中所有与集合有关的实现类都是这六个接口的实现类。

Collection接口:集合中每一个元素为一个对象,这个接口将这些对象组织在一起,形成一维结构。

List接口代表按照元素一定的相关顺序来组织(在这个序列中顺序是主要的),List接口中数据可重复。

Set接口是数学中集合的概念:其元素无序,且不可重复。(正好与List对应)

SortedSet会按照数字将元素排列,为“可排序集合”。

Map接口中每一个元素不是一个对象,而是一个键对象和值对象组成的键值对(Key-Value)。
Key-Value是用一个不可重复的key集合对应可重复的value集合。(典型的例子是字典:通过页码的key值找字的value值)。
例子:
key1—value1;
key2—value2;
key3—value3.
SortedMap:如果一个Map可以根据key值排序,则称其为SortedMap。(如字典)
!!注意数组和集合的区别:数组中只能存简单数据类型。Collection接口和Map接口只能存对象。
以下介绍接口:
List接口:(介绍其下的两个实现类:ArrayList和LinkedList)
ArrayList和数组非常类似,其底层①也用数组组织数据,ArrayList是动态可变数组。
底层:指存储格式。说明ArrayList对象都是存在于数组中。
注:数组和集合都是从下标0开始。
ArrayList有一个add(Object o)方法用于插入数组。
ArrayList的使用:(完成这个程序)
先import  java.util.*;
用ArrayList在一个数组中添加数据,并遍历。
ArrayList中数组的顺序与添加顺序一致。
只有List可用get和size。而Set则不可用(因其无序)。
Collection接口都是通过Iterator()(即迭代器)来对Set和List遍历。
通过语句:Iterator it=c.iterator(); 得到一个迭代器,将集合中所有元素顺序排列。然后可以通过iterator方法进行遍历,迭代器有一个游标(指针)指向首位置。
Interator有hasNext(),用于判断元素右边是否还有数据,返回True说明有。然后就可以调用next动作。Next()会将游标移到下一个元素,并把它所跨过的元素返回。(这样就可以对元素进行遍历)
集合中每一个元素都有对象,如有字符串要经过强制类型转换。
Collections是工具类,所有方法均为有用方法,且方法为static。
有Sort方法用于给List排序。
Collections.Sort()分为两部分,一部分为排序规则;一部分为排序算法。
规则用来判断对象;算法是考虑如何排序。
对于自定义对象,Sort不知道规则,所以无法比较。这种情况下一定要定义排序规则。方式有两种:
java.lang下面有一个接口:Comparable(可比较的)
可以让自定义对象实现一个接口,这个接口只有一个方法compareTo(Object o)
其规则是当前对象与o对象进行比较,其返回一个int值,系统根据此值来进行排序。
如 当前对象>o对象,则返回值>0;(可将返回值定义为1)
如 当前对象=o对象,则返回值=0;
如 当前对象<o对象,则返回值〈0。(可将返回值定义为-1)
我们通过返回值1和-1位置的调换来实现升序和降序排列的转换。

java.util下有一个Comparator(比较器)
它拥有compare(),用来比较两个方法。
要生成比较器,则用Sort中Sort(List,List(Compate))
第二种方法更灵活,且在运行的时候不用编译。

注意:要想实现comparTo()就必须在主方法中写上implement comparable.
练习:生成一个EMPLOYEE类,然后将一系列对象放入到ArrayList。用Iterator遍历,排序之后,再进行遍历。







集合的最大缺点是无法进行类型判定(这个缺点在JAVA1.5中已经解决),这样就可能出现因为类型不同而出现类型错误。
解决的方法是添加类型的判断。

LinkedList接口(在代码的使用过程中和ArrayList没有什么区别)
ArrayList底层是object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。
而在LinkedList的底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。
双向循环链表的查询效率低但是增删效率高。所以LinkedList具有查询效率低但增删效率高的特点。
ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。
LinkedList经常用在增删操作较多而查询操作很少的情况下:队列和堆栈。
队列:先进先出的数据结构。
堆栈:后进先出的数据结构。
注意:使用堆栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。
LinkedList提供以下方法:(ArrayList无此类方法)
addFirst();   
removeFirst();
  addLast();
  removeLast();
在堆栈中,push为入栈操作,pop为出栈操作。

Push用addFirst();pop用removeFirst(),实现后进先出。
用isEmpty()--其父类的方法,来判断栈是否为空。

在队列中,put为入队列操作,get为出队列操作。
Put用addFirst(),get用removeLast()实现队列。

package com.blackhorse.xinglefly;

/**
* Stack
* 写一个类用来模拟栈这种数据结构,要求底层使用数组存储数据,并给出相应的进栈和出栈的方法
* @author xinglefly
* @version 1
*/public class Stack {
private int arr [];
private int count;
public Stack(int size){
arr = new int[size];
count = 0;
}
public boolean push(int n){
if(count==arr.length){
System.out.println("stack is full already");
return false;
}
arr[count++]=n;
return true;
}
public int pop(){
if(count==0){
System.out.println("stack has empty");
return -1;
}
return arr[--count];
}
public void showMe(){
System.out.print("[");
for(int i=0;i<count;i++){
System.out.print(arr[i]+",");
}
System.out.println("]");
}

public static void main(String[] args){
Stack s = new Stack(3);
s.push(4);
s.push(3);
s.push(1);
s.push(2);
s.showMe();

s.pop();
s.pop();
s.pop();
s.pop();
s.showMe();
}

}


List接口的实现类(Vector)(与ArrayList相似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)
结论:在考虑并发的情况下用Vector(保证线程的安全)。
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。

面试经验(知识点):
java.util.stack(stack即为堆栈)的父类为Vector。可是stack的父类是最不应该为Vector的。因为Vector的底层是数组,且Vector有get方法(意味着它可能访问到并不属于最后一个位置元素的其他元素,很不安全)。
对于堆栈和队列只能用push类和get类。
Stack类以后不要轻易使用。
!!!实现堆栈一定要用LinkedList。
(在JAVA1.5中,collection有queue来实现队列。)




Set-HashSet实现类:
遍历一个Set的方法只有一个:迭代器(interator)。
HashSet中元素是无序的(这个无序指的是数据的添加顺序和后来的排列顺序不同),而且元素不可重复。
在Object中除了有final(),toString(),equals(),还有hashCode()。
HashSet底层用的也是数组。
当向数组中利用add(Object o)添加对象的时候,系统先找对象的hashCode:
int hc=o.hashCode(); 返回的hashCode为整数值。
Int I=hc%n;(n为数组的长度),取得余数后,利用余数向数组中相应的位置添加数据,以n为6为例,如果I=0则放在数组a[0]位置,如果I=1,则放在数组a[1]位置。如果equals()返回的值为true,则说明数据重复。如果equals()返回的值为false,则再找其他的位置进行比较。这样的机制就导致两个相同的对象有可能重复地添加到数组中,因为他们的hashCode不同。
如果我们能够使两个相同的对象具有相同hashcode,才能在equals()返回为真。
在实例中,定义student对象时覆盖它的hashcode。
因为String类是自动覆盖的,所以当比较String类的对象的时候,就不会出现有两个相同的string对象的情况。
现在,在大部分的JDK中,都已经要求覆盖了hashCode。
结论:如将自定义类用hashSet来添加对象,一定要覆盖hashcode()和equals(),覆盖的原则是保证当两个对象hashcode返回相同的整数,而且equals()返回值为True。
如果偷懒,没有设定equals(),就会造成返回hashCode虽然结果相同,但在程序执行的过程中会多次地调用equals(),从而影响程序执行的效率。

我们要保证相同对象的返回的hashCode一定相同,也要保证不相同的对象的hashCode尽可能不同(因为数组的边界性,hashCode还是可能相同的)。例子:
public int hashCode(){
  return name.hashcode()+age;
}
这个例子保证了相同姓名和年龄的记录返回的hashCode是相同的。

使用hashSet的优点:
hashSet的底层是数组,其查询效率非常高。而且在增加和删除的时候由于运用的hashCode的比较开确定添加元素的位置,所以不存在元素的偏移,所以效率也非常高。因为hashSet查询和删除和增加元素的效率都非常高。
但是hashSet增删的高效率是通过花费大量的空间换来的:因为空间越大,取余数相同的情况就越小。HashSet这种算法会建立许多无用的空间。
使用hashSet接口时要注意,如果发生冲突,就会出现遍历整个数组的情况,这样就使得效率非常的低。

package com.blackhorse.xinglefly;

import java.util.HashSet;
import java.util.Set;

/**
* new一个hashset,插入employee对象,不允许重复,并且遍历出来。
* @author xinglefly
* @version 1
*/
class Employee{
private String name;
private int age;
public Employee(String name,int age){
this.name=name;
this.age=age;
}
public boolean equals(Object obj){
if(obj !=null && (obj instanceof Employee))
if(this.age == ((Employee)obj).age)
return true;
return false;
}
public int HashCode(){
// return name.hashCode()+age;
return (new Integer(age).hashCode());
}
public String toString(){
return name+"----"+age;
}
}
public class CollectionSet {
public static void main(String[] args){
Employee p1 = new Employee("张三",12);
Employee p2 = new Employee("李四",18);
Employee p3 = new Employee("王五",25);
Employee p4 = new Employee("王五",25);

Set<Employee> s = new HashSet<Employee>();
s.add(p1);
s.add(p2);
s.add(p3);
s.add(p4);
System.out.println(s);
System.out.println(s.iterator());

System.out.println(p1.HashCode());
System.out.println(p2.HashCode());
System.out.println(p3.HashCode());
System.out.println(p3.HashCode()==p4.HashCode());//set值不能重复,无排序
}
}

添加知识点:
集合对象存放的是一系列对象的引用。
例:
Student S
Al.add(s);
s.setName(“lucy”);
Student s2=(Student)(al.get(o1));
可知s2也是s。





SortedSet可自动为元素排序。
SortedSet的实现类是TreeSet:它的作用是字为添加到TreeSet中的元素排序。

练习:自定义类用TreeSet排序。
与HashSet不同,TreeSet并不需要实现HashCode()和equals()。
只要实现compareable和comparTor()接口可以实现过滤功能。
package com.blackhorse.xinglefly;

import java.util.TreeSet;

/**
* 定义一个学生类, 需要有姓名, 年龄, 考试成绩三个成员属性. 属性(成员变量)需要私有并提供get, set方法, 可以通过构造函数进行初始化。
* @author xinglefly
* @version 1
*/
class StudentTest{
public static void main(String [] args){
TreeSet<Student> tree = new TreeSet<Student>();
tree.add(new Student("张三",22,89));
tree.add(new Student("李四",23,78));
tree.add(new Student("王五",32,54));
tree.add(new Student("赵六",12,26));
tree.add(new Student("牛气",52,100));
for(Student s1:tree){
System.out.println(s1);
}
}
}


class Student implements Comparable<Student>{

private String name;
private int age;
private int score;
public Student(String name,int age,int score){
this.name=name;
this.age=age;
this.score=score;
}
public boolean equals(Object obj){
if(!(obj instanceof Student))
throw new ClassCastException("次类型不是Student类型");
Student stu = (Student)obj;
return this.name.equals(stu.name)&&this.age==stu.age;
}
public int HashCode(){
return name.hashCode()+age;
}

public int compareTo(Student s) {
int num = new Double(this.score).compareTo((new Double(s.score)));
if(num==0){
if(this.name.compareTo(s.name)==0)
return new Integer(this.age).compareTo(new Integer(s.age));

return this.name.compareTo(s.name);
}

return num;
}

public String toString(){
return "name="+name+","+age+","+score;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}

如果要查询集合中的数据,使用Set必须全部遍历,所以查询的效率低。使用Map,可通过查找key得到value,查询效率高。
集合中常用的是:ArrayList,HashSet,HashMap。其中ArrayList和HashMap使用最为广泛。

使用HashMap,put()表示放置元素,get()表示取元素。

遍历Map,使用keySet()可以返回set值,用keySet()得到key值,使用迭代器遍历,然后使用put()得到value值。
上面这个算法的关键语句:
Set s=m.keySet();
Interator it=new interator();
Object key=it.next();
Object value=m.get(key);

注意:HashMap与HashCode有关,用Sort对象排序。
如果在HashMap中有key值重复,那么后面一条记录的value覆盖前面一条记录。

Key值既然可以作为对象,那么也可以用一个自定义的类。比如:
m.put(new sutdent(“Liucy”,30),”boss”)
如果没有语句来判定Student类对象是否相同,则会全部打印出来。



当我们用自定义的类对象作为key时,我们必须在程序中覆盖HashCode()和equals()。

注:HashMap底层也是用数组,HashSet底层实际上也是HashMap,HashSet类中有HashMap属性(我们如何在API中查属性)。HashSet实际上为(key.null)类型的HashMap。有key值而没有value值。

正因为以上的原因,TreeSet和TreeMap的实现也有些类似的关系。
注意:TreeSet和TreeMap非常的消耗时间,因此很少使用。
我们应该熟悉各种实现类的选择——非常体现你的功底。

HashSet VS TreeSet:HashSet非常的消耗空间,TreeSet因为有排序功能,因此资源消耗非常的高,我们应该尽量少使用,而且最好不要重复使用。
基于以上原因,我们尽可能的运用HashSet而不用TreeSet,除非必须排序。
同理:HashMap  VS  TreeMap:一般使用HashMap,排序的时候使用TreeMap。
HashMap VS Hashtable(注意在这里table的第一个字母小写)之间的区别有些类似于ArrayList和Vector,Hashtable是重量级的组件,在考虑并发的情况,对安全性要求比较高的时候使用。

Map的运用非常的多。


使用HashMap(),如果使用自定义类,一定要覆盖HashCode()和equals()。

重点掌握集合的四种操作:增加、删除、遍历、排序。

猜你喜欢

转载自xinglefly.iteye.com/blog/1673151