[置顶] JavaSE学习笔记_12:Java集合框架

时间:2021-07-26 09:50:57

Java集合框架

 

集合框架:

 [置顶]        JavaSE学习笔记_12:Java集合框架

 

集合框架中有许多类和接口,它们都是容器,就像数组一样,可以存取很多数据。但是比数组灵活,它存储的数据类型任意(泛型。可以指定)(比如可以字符串,存类对象。其实字符串也是类对象,只不过是java已经定义好多类。如果要存自定义类的对象,必须要要将这个类先描述出来。且用不同的容器存储需要复写父类中的一些方法,好让集合中的方法或者其他方法在底层调用它们。还有在迭代的时候注意对象强转的动作,才能调用自定义类中的内容。最后,需要注意的就是用集合存储自定义类的对象,集合中真正存储的是对象的引用,而不是对象的实体。),存取空间无限大。每一个容器存取数据的方式都不同,称之为:数据结构。而且用该框架中的容器存储数据,打印容器中的数据是格式打印的。

用左边中的容器存打印格式:[元素1、元素2、元素3,...];

用右边中的容器存打印格式:<key1=value1,...>。这里可以看出两边的容器存取的数据格式是不一样的。而且取容器中的元素用它自己特有的方式,就是利用迭代器。

 

泛型概述:

泛型格式:通过<>来定义要操作的引用数据类型。

当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。Iterator<>中的类型与其保持一致。

泛型:jdk1.5版本以后新特性。用于解决安全问题,是一个安全机制。

好处:

(1)将运行时期出现问题ClassCastException,转移到编译时期,让程序员及早发现问题,并进行修改。让运行时问题减少。

(2)避免了强制转化的麻烦。

如:TreeSet和Iterator就是泛型类。

import java.util.*;

class GenericDemo

{

public static void main(String[] args)

{

TreeSet<String> ts=new TreeSet<String>(new LenComparator());//使用该泛型类的时候,就要明确要 //操作的类型。

ts.add(“abcd”);

ts.add(“cc”);

ts.add(“cba”);

ts.add(“aaa”);

ts.add(“z”);

ts.add(“haha”);

 

Iterator<String> it=ts.iterator();//与指定类型 //对应

While(it.hasnext())

{

String s=it.next();

System.out.println(s);

}

}

}

class LenComparator extends Comparator<String>

{

public int compare(String o1,String o2)

{

int num=new Integer(o2.length()).CompareTo(

new Integer(o1.length())

)

if(num==0)

return o2.compareTo(o1);

return num;

}

}

 

泛型应用:泛型类、泛型方法、泛型接口

(1)泛型类。

当类中要操作的引用数据类型不确定的时候就定义泛型类。

早期建立Object类来完成扩展。

现在建立泛型来完成扩展。

如:泛型类

class Tool<QQ>

{

private QQ q;

public void setObject(QQ q)

{

This.q=q;

}

public void getObject()

{

return q;

}

}

(2)泛型方法。

由泛型类的定义可知:泛型类的定义在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体的类型后,所有要操作的类型就已经固定。为了让不同方法可以操作不同类型,而且类型不确定。那么可以将泛型定义在方法上。

A、非静态方法

class Demo

{

public <T> void show(T t)

{

System.out.println(“show:”+t);

}

public <Q> void show(Q q)

{

System.out.println(“print:”+t);

}

}

温馨提示:泛型方法和泛型类是不冲突的。如:

class Demo

{

public void show(T t)

{

System.out.println(“show:”+t);

}

public <Q> void print(Q q)

{

System.out.println(“print:”+q);

}

}

class GenericDemo

{

public static void main(String[] args)

{

Demo<String> d=new Demo<String>();

d.show(“haha”);

 

//d.show(4);错的。

d.print(5);

d.print(“haha”);

}

}

B、静态方法

静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。

 

(3)泛型接口。

如:

interface Inter<T>

{

void show(T t);

}

class InterImpl implements Inter<T>

{

public void show(T t)

{

System.out.println("show:"+t);

}

}

class GenericDemo

{

public static void main(String[] args)

{

InterImpl<Integer> i=new InterImpl<Interger>();

i.show(4);

}

}

 

泛型的高级应用:泛型限定

(1)

?:通配符。也可以理解为占位符。它可以代替任意类型。如:

ArrayList<String> al=new ArrayList<String>();

public static void printColl(ArrayList<?> al)

{

Iterator<?> it=al.iterator();

while(it.hasNext())

{

System.out.println(it.next());

}

}

也可以定义成泛型。观察两种方式的区别:

ArrayList<String> al=new ArrayList<String>();

public static void printColl(ArrayList<T> al)

{

Iterator<T> it=al.iterator();

while(it.hasNext())

{

T t=it.next();

System.out.println(t);

}

}

 

(2)

a. ?Extends E:可以接收E类型或者E类型的子类型。上限。

b. ?Super E:可以接收E类型或者E类型的父类型。下限。

如:

a、

ArrayList<String> al=new ArrayList<String>();

public static void printColl(ArrayList<? extends Fu> al)

{

Iterator<? extends Fu> it=al.iterator();

while(it.hasNext())

{

System.out.println(it.next());

}

}

b、

ArrayList<String> al=new ArrayList<String>();

public static void printColl(ArrayList<? super Zi> al)

{

Iterator<? super Zi> it=al.iterator();

while(it.hasNext())

{

System.out.println(it.next());

}

}

 

温馨提示:开发时,程序代码的第一行要导入java.util.*;

左边:

Collection:

(1)方法

添加元素:void add(E e);

获取元素个数:int size();

删除元素:void remove();

清空集合:void clear();

判断元素是否存在:boolean contains(E e);

判断集合是否为空:boolean isEmp();

取与另一个集合中相同的元素:void retainAll(Collection c);

取与另一个集合中不同的元素:void removeAll(Collection c);

(2)迭代器

Iterator it=集合对象.iterator();

While(it.hasNext())

{

System.out.println(it.next());

}

it可以调用集合对象所属集合中的方法,对集合进行操作。

Ps:迭代器只能是判断一次,取一次,类似于上面这种形式。

而不能判断一次,取两次,类似于下面这种形式。

While(it.hasNext())

{

System.out.println(it.next());

System.out.println(it.next());

}

因为如果,集合中只有一个元素的时候,判断是虽然有下一元素,但是不够打印的,因为要打印两个。会出错。

 

|--List

元素是有序的,元素可以重复。因为该集合体系中有索引。

(1)方法

凡是可以操作角标的方法都是该集合体系中特有的方法。

如:

void add(index,element);//添加一个元素

void add(index,Collection);//添加一堆元素

 

void remove(index);//删除元素

 

void set(index,element);//修改元素

 

 E get(index);//获取元素

 Collection subList(from,to);

 

(2)迭代器

List有自己特有的迭代器。ListIterator,它是Iterator的子接口。该迭代器产生的对象可以调用更多的方法对集合进行操作。

ListIterator li=集合对象.ListIterator();

while(li.hasNext())

{

//比如可以写下列语句

Object obj=li.next();

if(obj.equals(“java”))

li.set(“java00”)

}

 

(面试)

List下面有三个类:

① ArrayList

线程不同步。(jdk1.2)底层是数组结构。可变长度数组。

增删慢,查询快。

② LinkedList

底层是链表结构。增删快。查询慢。

③ Vector

线程同步。(jdk1.0)底层是数组结构。被ArrayList替代。

 

ArrayList

(1)方法

无特有方法。

(2)迭代器

无特有迭代器。

 

LinkedList

(1)方法

它有自己特有的方法。

(面试)

void addFirst();//始终在头部添加元素。

void addLast();//始终在尾部添加元素。

 

E getFirst();//始终获取头部元素。

E getLast();//始终获取尾部元素。

 

E removeFirst();//始终获取头部元素。(但元素被删除了)

E  removeLast();//始终获取尾部元素。(但元素被删除了)

(2)迭代器

它没有自己特有的迭代器。

Vector

(1)方法

它没有自己特有的方法。

(2)迭代器

它有自己特有的迭代器:枚举。但是被Iterator取代了。

Enumeration en=集合对象.elements();

while(en.hasMoreElements)

{

System.out.println(en.nextElement());

}

 

由于List集合中元素都是有序的,但是有重复,因此我们想去重复元素。

例如:

import java.util.*;

class Person

{

private String name;

private int age;

Person(String name,int age)

{

this.name=name;

this.age=age;

}

//重写Object类中的equals();

public boolean  equals(Object obj)

{

if(!(Obj instanceof Person))

return false;

Person p=(Person)obj;

//测试后面的contains()在调用该equals()。

//(remove()底层也是在调用equals())

Systeme.out.println(this.name+“....”+p.name);

return this.name.equals(p.name)&&this.age==p.age;

}

public String getName()

{

return name;

}

public int getAge()

{

return age;

}

}

class ArrayListTest

{

public static void sop(Object obj)

{

System.out.println(obj);

}

public static void main(String[] args)

{

ArrayList al=new ArrayList();

al.add(new Person(“lisi01”,30));

al.add(new Person(“lisi02”,32));

al.add(new Person(“lisi04”,35));

al.add(new Person(“lisi03”,33));

al.add(new Person(“lisi04”,35));

 

al.singleElement(al);

 

Iterator it=al.iterator();

while(it.hasNext())

{

Person p=(Person)it.next();

sop(p.getName()+”::”+p.getAge());

}

}

Public static ArrayList singleElement(ArrayList al)

{

//定义一个临时容器。

ArrayList newAl=new ArrayList();

Iterator it=al.iterator();

while(it.hasNext())

{

Object obj=it.next();

if(!newAl.contains(Obj))

newAl.add(obj);

}

return newAl;

}

}

小技巧:List集合体系中存储自定义类对象,想去重复元素自定义类一定要重写equals()。

如:

public boolean  equals(Object obj)

{

if(!(Obj instanceof Person))

return false;

Person p=(Person)obj;

//测试后面的contains()在调用该equals()。

//(remove()底层也是在调用equals())

Systeme.out.println(this.name+“....”+p.name);

return this.name.equals(p.name)&&this.age==p.age;

}

 

|--Set

元素是无序的,元素不可以重复。

无序:无序是指不按照添加的顺序存取,但根据存储容器的特性可以对元素按照自己的一套思路进行排序存放在内存中。边排边存。

不可重复:元素不能一样。

(1)方法

无特有方法

(2)迭代器

无特有迭代器

 

HashSet

它是如何保证元素无序和元素唯一性的:

HashSet的底层数据结构是哈希表。哈希表的特点:第一、存的时候是按哈希值的大小而不是按你存的顺序来存。第二、哈希值重复时,还有一次校验形式:两个是不是同一个对象,不是则在该地址下顺延,再来一个地址值时,与哈希表中其他地址值比较。

(1)方法

无特有方法

(2)迭代器

无特有迭代器

小技巧:HashSet集合体系中存储自定义类对象,自定义类一定要重写hashcode()和equals()。如果元素的哈希值相同,才会调用equals();如果元素的哈希值不同,不会调用equals()。[由于HashSet集合的特点,对于判断元素是否存在,以及删除等操作,依赖的方法都是元素的hashCode()和equals()方法。]

如:

public int hashcode()

{

return name.hashCode()+age*34;

}

public boolean equals(Object obj)

{

if(!(0bj instanceof Student))

Throw new ClassCastException(“类型不匹配”);

Student s=(Student)obj;

Return this.name.equals(s.name)&&this.age==s.age;

}

 

TreeSet

它是如何保证元素无序和元素唯一性的:

TreeSet集合的底层结构是二叉树,因为它存储效率高。它存储元素时,若该元素比父结点大,放在父结点的右边;若该元素比父结点小,放在父结点的左边。因此与父结点相同的就存不进去。因此如果TreeSet集合体系要存储自定义类对象,必须保证对象具备可比性。

(1)方法

无特有方法

(2)迭代器

无特有迭代器

 

小技巧:TreeSet集合体系要存储自定义类对象时,必须让元素具备可比较性。有两种方式:

(1)让自定义类实现Comparable接口,使元素具备可比性。重写public int CompareTo(Object obj)方法。

如:

public int compareTo(Object obj)

{

if(!(obj instanceof Student))

Throw new RuntimeException(“不是学生对象”);

Student s=(Student)obj;

//验证compareTo()在底层被调用。

System.out.println(this.name+“....compare...”+s.name);

if(this.age>s.age)

return 1;

if(this.age==s.age)

Return this.name.compareTo(s.name);

return -1;

 

//return 1;始终比前者小

//return -1;始终比前者大

//return 0;始终和前者相等,容器中最終只有一个元素

}

(2)定义一个类,实现Comparator接口,覆盖comPare方法。这种方式是当元素自身不具备比较性,或者具备的比较性不是所需要的。这时需要让容器自身具备比较性。定义比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。(常用方式)

如:

class Mycompare implements comparator

{

public int compare(Object o1,Object o2)

{

Student s1=(Student)o1;

Student s2=(Student)o2;

int num=s1.getName.compareTo(s2.getName());

if(num==0)

{

return new Integer(s1.getAge()).compareTo(

new Integer(s2.getAge())

);

/*

if(s1.getAge()>s2.getAge())

return 1;

if(s1.getAge()==s2.getAge())

return 0;

if(s1.getAge()<s2.getAge())

return -1;

*/

}

return num;

}

}

右边:

Map

接口:Map<K,V>

K:此映射所维护键的类型。

V:映射值的类型。

Map集合体系中存储的是键值对<key,value>。Map集合中的键要保持唯一性。Map集合没有直接取出元素的方法(迭代器),而是先转成Set集合,再通过迭代取出元素。

(1)方法

① 添加:

V put(K key,V value):覆盖地添加,返回被覆盖的值。

void putAll(Map<? extends K,? extends V> m)

② 删除:

void clear()

V remove(Object key)

③ 判断:

boolean containsValue(Object value )

boolean containsKey(Object key)

boolean isEmpty()

④ 获取:

V get(Object key)

int size()

Collection<T> value():返回集合中所有键所对应的值。

(2)迭代器

无特有的迭代器。但有自己的取元素方式。

方法一:使用keySet。将map集合中所有键存入到Set集合,因为Set具备迭代器。所以通过迭代方式取出所有的键,再根据get方法获取每一个键所对应的值。

如:

import java.util.*;

class MapDemo

{

public static void main(String[] args)

{

Map<String,String> map=new HashMap<

String,String>();

map.put("01","zhangsan1");

mpa.put("02","zhangsan2");

mpa.put("03","zhangsan3");

mpa.put("04","zhangsan4");

mpa.put("05","zhangsan5");

 

//先获取map集合所有键的Set集合,keySet()

Set<String> keySet=map.keySet();

 

//有了Set集合,就可以获取其迭代器。、

Iterator<String> it=keySet.iterator();

while(it.hasNext())

{

String key=it.next();

//有了键可通过map集合get方法获取其对应的值。

String value=map.get(key);

   System.out.println("key:"+key+"value:"+value);

}

}

}

 

方法二:使用entrySet。

将map集合中的映射关系存到Set集合中,而这个关系的数据类型就是Map.Entry<K,V>。

如:

import.java.util.*;

class MapDemo

{

public static void main(String[] args)

{

Map<String,String> map=new HashMap<

String,String>();

map.put("01","zhangsan1");

mpa.put("02","zhangsan2");

mpa.put("03","zhangsan3");

mpa.put("04","zhangsan4");

mpa.put("05","zhangsan5");

 

//将Map集合中的映射关系取出,存入到Set集合中。

Set<Map.Entry<String,String>> entrySet=

                   map.entrySet();

      Iterator<Map.Entry<String,String>> it=

entrySet.iterator();

while(it.hasNext())

{

map.Entry<String,String> me=it.next();

String key=me.getKey();

String value=me.getValue();

System.out.println(key+":"+value);

}

}

}

 

|--HashTable:底层是哈希表数据结构,不可以存null键null值。该集合是线程同步的。jdk1.0,效率低。

(1)方法

无特有方法。

(2)迭代器

无特有迭代器。

 

|--HashMap:底层是哈希表数据结构,允许使用null值和null键,该集合是不同步的。jdk1.2,效率高。

(3)方法

无特有方法。

(4)迭代器

无特有迭代器。

 

|--TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。

(5)方法

无特有方法。

(6)迭代器

无特有迭代器。

 

温馨提示:其实Map的用法和Set的用法可以照套,几乎是一样的。唯一的区别就是,Map集合中存的是映射关系,Set集合中存的是单值。底层所要调用的方法也是一样的,只不过是针对的是映射关系中的value或者还key。

 

Map拓展:

比如:

传智播客有两个班,每个班有若干个学生,

“yure”    “01”  “zhangsan”

“yure”    “02”  “wangwu”

“jiuye”   “03”   “lisi”

“jiuye”   “04”    “zhaoliu”

import java.util.*;

class MapDemo

{

public static void main(String[] args)

{

HashMap<String,HashMap<String,String>>

  czbk=new 

HashMap<String,HashMap<String,String>>();

 

HashMap<String,String> yure=new 

HashMap<String,String>();

 

HashMap<String,String> jiuye=new 

HashMap<String,String>();

 

czbk.put("yure",yure);

czbk.put("jiuye",jiuye);

 

yure.put("01","zhangsan");

yure.put("02","wangwu");

jiuye.put("03","lisi");

jiuye.put("04","zhaoliu");

 

//getStudentInfo(yure);

//getStudentInfo(jiuye);

//通过遍历czbk集合。获取所有的教室。

Iterator<String> it=czbk.keySet().iterator();

while(it.hasNext())

{

String roomName=it.next();

 

HashMap<String,String> room=czbk.get(roomName);

 

System.out.print(roomName);

getStudentInfo(room);

}

public static void getStudentInfo(HashMap<

String,String>roomMap)

{

Iterator<String> it=roomMap.keySet().iterator();

while(it.hasNext())

{

String id=it.next();

String name=roomMap.get(id);

System.out.println(id+":"+name);

}

}

}

}

思考:如果是:

“yure”    new Student(“01”,“zhangsan”)

“yure”    new Student(“02”,“wangwu”)

“jiuye”   new Student(“03”,“lisi”)

“jiuye”   new Student(“04”,“zhaoliu”)

怎么办呢?

我们首先还是定义一个HashMap,形式是:

HashMap<String,List<Student>> czbk=new 

HashMap<String,List<Student>>();

然后就是:

List<Student> yure=new ArrayList<Student>();

List<Student> jiuye=new ArrayList<Student>();

还有就是在getStudentInfo()中不同的是:

Iterator<Student> it=list.iterator();

while(it.hasNext())

{

Student s=it.next();

System.out.println(s);

}

 

集合中的工具类

(1)Collection

用于List集合体系的静态方法:

排序:

① sort(集合对象):按java识别的机制排序。

② sort(集合对象,比较器对象):按自定义的机制排序。

 

求最大值:

③ max(集合对象)

④ max(集合对象,比较器对象)

Ps:求出最大值也需要元素具备可比性。

 

二分查找:

⑤ int binarySearch(集合对象,需要查找的元素)

⑥ int binarySearch(集合对象,需要查找的元素,比较器对象)

Ps:二分查找的前提是元素有序,因此先对元素进行排序。若查找的元素集合中不存在的话,返回的就是(-插入量(min)-1)。

 

替换:

⑦ fill(集合对象,替换的元素):将集合中所有的元素都替换成替换的元素。

⑧ replaceAll(集合对象,被替换的元素,替换的元素):将集合中部分元素替换成替换的元素。

 

反转:

⑨ reverse(集合对象);

 

置换:

⑩ shuffle(集合对象):将列表中的位置进行随机的置换。应用:洗牌,掷骰子。

11 swap(集合对象,a,b):将:List集合中位置a和位置b上的元素进行交换。

 

用于Set集合体系的静态方法:

① reverseOrder()

比如:

TreeSet<String> ts=new TreeSet<String>

(Collection.reverse());这样

  打印出来的元素顺序就不是按照java识别的机制,如“aa” 必须排放在“ab”前面。而是恰恰反过来的。当然复杂的 方式是将自己定义一个比较器,存的时候就调用底层的  compare()方法。对应的还有:

② reverseOrder(比较器对象)

比如:

TreeSet<String> ts=new TreeSet<String>

(Collection.reverse(new strLenComparator()));

 

(2)Arrays

记忆:

int[] a=new int[]{1,2,3,4};

System.out.println(Array.toString(a));

结果:[1,2,3,4]。

功能:是将数组a转化成字符串数组。

 

把数组变成List集合:asList(数组对象)

可以使用集合的思想和方法来操作数组中的元素。但是不 可以使用集合的增删方法,因为数组的长度是固定的。

如:

String[] arr={"abc","cc","kkkk"};

List<String> list=Arrays.asList(arr);

 

ps:

如果数组中的元素都是对象。那么变成集合时,数组中的 元素就直接转成集合中的元素。如果数组中的元素都是基 本数据类型,那么将该数组作为集合中的元素存在。

int[] nums={2,3,5};

Integer[] nums={2,3,5};

 

List<int[]> li1=Arrays.asList(nums);

List<Integer> li2=Arrays.asList(nums);

 

sop(li1);//I@de6ced

sop(li2);//[2,3,5]

 

将集合变成数组:toArray(new 集合中元素类型(集合对象.size()))

ArrayList<String> al=new ArrayList<String>;

 

al.add("abc1");

al.add("abc2");

al.add("abc3");

 

String[] arr=al.toArray(new String[al.size()]);

System.out.println(Arrays.toString(arr));

 

1、指定类型的数组到底定义多长呢?

当指定类型的数组长度小于了集合的size,那么该方法 就会创建一个新的数组。长度为集合的size。

 

当指定类型的数组大于了集合的size,就不会新创建数 组,而是使用传递进来的数组。

 

所以创建一个刚刚好的数组最优。

 

2、为什么要将集合变数组?

为了限定对元素的操作。不需要进行增删了。

 

 

高级for循环:

格式:

for(数据类型 变量名 : 被遍历的集合(Collection)或者数组)

{

}

ArrayList<String> al=new ArrayList<String>();

 

al.add("abc1");

al.add("abc2");

al.add("abc3");

/*

Iterator<String> it=al.iterator();

while(it.hasNext())

{

System.out.println(it.next());

}

*/

for(String s : al)

{

System.out.println(s);

}

//s指向了某位置后,则s被赋值为该位置上的值。

 

Ps:高级for循环对集合进行遍历。只能获取集合元素。 但是不能对集合进行操作。迭代器除了遍历,还可以进行 remove集合中元素的动作,如果是用ListIterator,还 可以在遍历的过程中对集合进行增删改查的动作。

 

可变参数:

比如:

(1)arr是一个数组。它可以直接接收要操作的元素,而 且个数不限,隐式将这些元素封装成数组。

public static void main(String[] args)

{

show();

show(1);

show(1,2);

}

public static void show(int...arr)

{

...

}

(2)可变参数一定要定义在参数列表的最后面。

public static void show(String s,int...arr)

{

...

}

 

 

 

静态导入:

比如之前我们在讲集合当中的工具类时,有一个对集合中元素进行排序的静态方法sort();如果我们要使用它的话,应当采用类似于Collection.sort(al)这样的格式,但是如果我们在程序的第一行加上:

import java.util.Collection.*;则此时我们要使用该方法时,直接sort(al);其他的方法使用可以同理得出。但是唯一注意的就是,toString(arr)。即使我们导入了包:

java.util.Arrays.*;我们也不能省略Arrays。原因是任何类都是Object类的子类,继承里边非静态方法toString(),如果省略了Arrays,默认是使用Object里边的toStirng(),则此时必须要对象调用。(如果toString()在main()中被调用的话)



2015-12-072015-12-15