前言
主要介绍Java集合的两大种类:Collection和Map,包括它们各自主要实现类的特点以及主要方法。同时还包括Java泛型机制的介绍。
目录
五、ArrayList、LinkedList和Vector的比较
一、Java集合概述
集合是一类容器,一类盛放数据的容器。之前学过的数组也是一个容器,可以看成是一种特殊的集合。Java中的集合与数组不相同的点在于,集合中不能直接存放基本数据类型的元素,存的都是对象的引用(也就是指向对象的地址)。往集合中放基本数据类型的元素时会自动装箱。同时、集合也是一种引用数据类型,它的引用也可以放到其它的集合之中。也就是说,集合中可以放集合。
Java中的所有集合都会自动扩容,不同集合的初始大小不同,数据结构也不同。
二、集合的分类
Java中的集合放在java.util包下
集合按存储方式可分为两大类:
1、单个方式存储
单个方式存储就是每个元素单独存放,它有个超级父接口:Colletion. 接口中有一个iterator( )方法,可以返回一个Iterator迭代器 ,使用迭代器可以实现对Collection类集合的遍历。
该接口下主要有两个分支:List和Set.
这两个都是接口,实现List接口的类主要有:ArrayList、LinkedList和Vector,实现List接口的类都有序(也就是存进去和拿出来的顺序一样,也有下标),且元素可重复。
实现Set的类主要是HashSet和实现了继承该接口的SortedSet接口的TreeSet。Set接口下的集合都无序且元素不可重复。实现SortedSet接口的类会自动排序。
关系可见下图:
2、键值对的方式存储
其实就是字典,该种存储方式的超级父接口是Map,Map集合与Collection集合之间并没有什么继承关系,二者属于不同的类别。Map集合以键值对的方式存储元素,它的键和值都是引用,依然不能是基本数据类型。
Map集合的键无序不可重复,实际上Set集合创建的对象就都是放在Map集合中的键部分,也就是说,Map集合的键是一个Set集合。当出现相同的键时,会将该键原来的值覆盖掉。
Map集合主要有两个实现类:HashMap和Hashtable,它们都借助了哈希表来完成存储。因此放在这两个集合中的元素,还有放在HashSet集合中的元素都最好重写equals()和hashCode()方法。HashMap是非线性安全的,效率高,使用较多。Hashtable是线性安全的,效率低。还有一个继承了Map的接口SortedMap,该接口下的实现类的键部分都会自动排序。它的一个实现类是TreeMap.
Map集合关系可见下图:
三、Collection接口的常用方法
1、public boolean add( Object obj )
利用该方法向Collection集合中添加元素。
2、 public int size( )
获取集合中的元素个数
3、public void clear( )
清空集合元素
4、public boolean contains( Object obj )
判断集合中是否包含某一元素
5、public void remove( Object obj )
删除集合中的某一元素
contains()和remove()方法都需要调用到存放对象的equals()方法。因此存进Collection集合中的元素最好重写equals()方法.
6、public boolean isEmpty( )
判断集合是否为空
7、public Object[ ] toArray( )
Collection集合转换为Object数组
import java.util.*;
public class Test01 {
public static void main(String[] args) {
Collection c = new ArrayList();
//使用add()方法向Collection集合中添加元素
c.add(new Object());
c.add(100);//自动装箱
c.add(66);
//使用size()方法获取集合的元素个数
System.out.println(c.size());//3
//使用contains()方法判断是否有某个元素
//使用remove()方法删除某个元素
//两个方法都需要用到元素的equals()方法
System.out.println(c.contains(100));//true
c.remove(100);
//使用toArray()将Collection集合转换为数组
Object[] o = c.toArray();
//使用clear()方法清空集合元素
c.clear();
System.out.println(c.size());//0
//使用isEmpty()方法判断集合元素是否为空
System.out.println(c.isEmpty());//true
}
}
8、迭代器
Collection类继承了Iterator接口,有iterator( )方法,所以所有实现该接口的集合也都有iterator( )方法,该方法返回一个迭代器对象。这种迭代器主要是三种方法:
public boolean hasNext( ):判断下一个元素是否存在
public Object next( ):迭代器前进并返回该元素
public void remove( ):删除迭代器当前指向元素
注意,迭代器获取到的只是集合当前的“快照”,一旦对当前集合进行增删改等操作,那么该“快照” 就失效了,就不能再去迭代集合。因此不能随便调用集合的remove( )方法,如果想要删除元素,可以使用迭代器的remove( )方法。
import java.util.*;
public class Test02 {
public static void main(String[] args) {
Collection c = new ArrayList();
//往集合中添加6个元素
c.add(new Object());
c.add(60);
c.add(new Object());
c.add(80);
c.add(new Object());
c.add(new Object());
//使用iterator()方法获取迭代器对象
Iterator iter = c.iterator();
while(iter.hasNext()){
Object obj = iter.next();
//c.remove(80);改变了集合的状态,迭代器失效,JVM报错
//可以使用迭代器的remove()方法
iter.remove();
}
System.out.println(c.size());//0
}
}
四、List类的一些特有方法
因为List集合有下标,所以它的特殊方法基本都和下标有关。
1、public void add( int index,E element )
向指定下标添加元素,这个E是泛型
2、public Object get( int index )
根据下标获取元素
3、public int indexOf( Object obj )和public int lastIndexOf( Object obj )
返回指定元素第一次(最后一次)出现的位置
4、public Object remove( int index )
删除指定下标元素
5、public Object set(int index,Object elment )
修改指定下标元素
import java.util.*;
public class Test03 {
public static void main(String[] args) {
List l = new ArrayList();
//添加指定下标的元素
l.add(0,10);
l.add(1,20);
l.add(2,30);
//获取指定下标元素
System.out.println(l.get(2));//30
//返回指定元素第一次出现位置
System.out.println(l.indexOf(20));//1
//修改指定元素的值
l.set(1,60);
System.out.println(l.get(1));//60
}
}
五、ArrayList、LinkedList和Vector的比较
ArrayList默认初始化容量是10,但是可以用有参数的构造方法传入初始容量。扩容时,扩大到原来的1.5倍。该集合使用最多
Vector默认初始化容量也是10,也可以用有参数的构造方法传入初始容量。
将非线性安全的ArrayList转为线性安全,需要用到工具类Collection中的Collections.synchronizedList(ArrayList集合);
LinkedList与ArrayList最大的区别就是数据结构上的差异,ArrayList的数组结构便于查找和修改,而LinkedList的双链表便于插入和删除。由于它是用链表存储的数据,所以不需要初始化容量和扩容。
此外,Collection中的集合可以利用各自的构造方法实现相互转换,如用ArrayList的
有参构造方法中传入一个Set类型的集合,就可以转换成ArrayList集合。
六、泛型机制
在JDK5之后,就有了泛型机制。泛型,顾名思义,就是类型不确定,也就是参数化类型。Java的泛型机制就是将类型用变量替代,主要用在方法和类上。并且,它只能代替引用数据类型,不能用于基本数据类型。
当泛型用在方法上时,格式是:[修饰符列表] <类型参数名> 返回值类型 方法名 (形式参数列表){ 方法体 }
public static <E> void fun(){
}
泛型用在类上时,格式是:[修饰符列表] class 类名 <类型参数名>{ 类体 }
用在类上,指定某种类型时,格式是:类名<指定数据类型> 引用 = new 类名<指定数据类型> ( )
public class Test04<E> {
public void fun(E i){
}
}
可以用泛型来指定集合的数据类型:
有泛型机制,但没指定数据类型时,默认为Object类。
import java.util.*;
public class Test05 {
public static void main(String[] args) {
List<String> f = new ArrayList<String>();
f.add("aaa");
//f.add(100)会报错
}
}
JDK8之后推出了自动泛型机制,也称为钻石表达式。就是在指定数据类型时可以只写前面的,后面的用<>代替。如:List<String> f = new ArrayList<>()。
七、Map接口的常用方法
1、public V put( K key,V value )
向集合中添加元素,这里的K和V都是在创建Map集合对象时指定的键类型和值类型
2、public Set<Map.Entry<K,V>> entrySet( )
将Map集合转化为Set集合,集合中的每个元素是Entry类型,Entry是Map的一个内部接口,得到的是键值对的映射项。用属性来存储键和值。可以使用每个元素的getKey( )和getValue( )来获得键和值。
3.public V get(Object key)
用键获取值
4、public int size( )
获取集合中的元素个数
5、public V remove(Object key)
通过键删除指定键值对
6、public void clear( )
清空集合
7、public boolean isEmpty( )
判断集合是否为空
8、public boolean containsKey(Object key)和public boolean containsValue(Object value)
判断集合是否包含某个键或者某个值
9、public Set<K> keySet( )
获取所有的键并返回一个Set集合。
10、public Collection<V> values( )
获取所有的value并返回一个Collection集合。
import java.util.*;
public class Test05 {
public static void main(String[] args) {
Map m = new HashMap<>();
//使用put()方法放入键值对
m.put(1,"a");
m.put(2,"b");
m.put(3,"c");
//使用get()方法通过键获取值
System.out.println(m.get(1));//a
//判断是否包含某个键或值
System.out.println(m.containsKey(6));//false
System.out.println(m.containsValue("a"));//true
//使用values()方法获取所有值
Collection v = m.values();
//使用keySet()方法获取所有键
Set k = m.keySet();
//将Map集合转换为Set集合
Set s = m.entrySet();
}
}
遍历Map的方法:
1、 增强for循环机制:JDK5之后增加的新机制,用于可迭代对象,比原来的普通for循环使用更加简单、灵活。
格式是:for( 可迭代对象中元素的数据类型 变量名:可迭代对象){...}
import java.util.*;
public class Test01 {
public static void main(String[] args) {
//使用增强for循环机制
//可迭代对象可以是数组或集合
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
//泛型未使用来指定具体数据类型是,元素类型默认为Object
for(Object i :c){
System.out.print(i+" ");//1 2 3
}
}
}
2、遍历Map集合的两种方法
使用键去获取值和直接将Map集合转换成Set集合后取值。
import java.util.*;
public class Test02 {
public static void main(String[] args) {
Map m = new HashMap();
m.put(1,"a");
m.put(2,"b");
m.put(3,"c");
//遍历集合的第一种方法:用键获取值
Set keys = m.keySet();
//有两种方式去遍历:
//使用迭代器
Iterator i = keys.iterator();
while(i.hasNext()){
Object key = i.next();
System.out.print(m.get(key)+" ");//a b c
}
System.out.println();//换行
//使用增强for循环
for(Object key:keys){
System.out.print(m.get(key)+" ");//a b c
}
System.out.println();//换行
//遍历集合的第二种方法:将Map集合转换为Set集合
Set<Map.Entry> items = m.entrySet();
//使用迭代器
Iterator<Map.Entry> j = items.iterator();
while(j.hasNext()){
Map.Entry item = j.next();
System.out.print(item.getValue()+" ");//a b c
}
System.out.println();//换行
//使用增强for循环
for(Map.Entry item:items){
System.out.print(item.getValue()+" ");//a b c
}
}
}
将Map集合转换成Set集合后去遍历获取值效率更高。原因是它把键和值组合在一起,只要找到了键就同时也找到了值。而第一种方法是把所有的键取出来之后,再通过键去找值。
注意,迭代器就只有继承了Iterator接口的Collection集合才有,增强for循环实质也是借助迭代器实现的,因此遍历Map集合的一个重要思路是先得到它的一个Collection集合。
八、HashMap、Properties和TreeMap
1、HashMap和Hashtable
HashMap和Hashtable都是使用的哈希表来完成数据的存储。而Map集合中参与哈希值运算的是键,所以键必须唯一不可重复。放在HashMap和Hashtable,以及HsahSet中的元素,都最好重写equals()方法和hashCode()方法(用来生成哈希值)。
class HashMapElement{
//放在利用哈希表完成存储的集合中的元素要重写equals()和hashCode()方法
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
}
HashMap集合中元素的键和值都可以是null,当键都是为null时,会将之前的null的值覆盖掉。而Hashtable的键和值都不能为null。
普通的利用哈希表来存储的数据结构是利用数组加单链表的形式实现的。Java在JDK8以后,当某个节点的单链表超过了8个元素时,就会自动转换成红黑树。小于6个元素时就又变成单链表。
2、Properties
Properties继承自Hashtable,它比较特殊的点在于,它的键和值都只能是String类型。它有两个常用方法:public void setProperty( )和public String getProperty( ),用来添加和获取元素。
import java.util.*;
public class Test03 {
public static void main(String[] args) {
Properties pro = new Properties();
//使用setProperty()方法放入元素
pro.setProperty("111","aaa");
pro.setProperty("222","bbb");
//使用getProperty()方法取出元素
System.out.println(pro.getProperty("111"));//bbb
}
}
该类常用来配置文件,通常用来读取对应的 .properties文件中的内容。
3、TreeMap
这类集合会将元素(key部分)排序。在排序时会使用元素的compareTo( )方法或者使用构造方法传入的比较器来排序。因此放入该集合中的元素需要实现java.lang.Comparable接口,实现compareTo( )方法,或者在创建集合对象时,调用有参的构造方法,传入一个实现了java.util.Comparator接口的构造器。
import java.util.*;
public class Test04{
public static void main(String[] args) {
//1、实现Comparable接口和compareTo()方法
TreeMap tm1 = new TreeMap();
tm1.put(new Element(1),1);
tm1.put(new Element(6),2);
tm1.put(new Element(3),3);
//遍历tm1
Set<Map.Entry> items1 = tm1.entrySet();
for(Map.Entry item:items1){
System.out.println("key = "+item.getKey()+" "+"value = "+item.getValue()+" ");
}
System.out.println("-------------------------------------");
//2、传入一个构造器
Comparator<Element2> comparator = new Comparator<Element2>() {
@Override
public int compare(Element2 o1, Element2 o2) {
if(o1.getAge()==o2.getAge())
{
String s1 = o1.getName();
String s2 = o2.getName();
return s1.compareTo(s2);
}
return o1.getAge()-o2.getAge();
}
};
TreeMap tm2 = new TreeMap(comparator);
tm2.put(new Element2(20,"xl"),3);
tm2.put(new Element2(20,"ac"),4);
tm2.put(new Element2(19,"zs"),5);
//遍历tm2
Set<Map.Entry> items2 = tm2.entrySet();
for(Map.Entry item:items2){
System.out.println("key = "+item.getKey()+" "+"value = "+item.getValue()+" ");
}
}
}
class Element implements Comparable<Element>{
private int n;
public Element() {
}
public Element(int n) {
this.n = n;
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
//实现Comparable接口和compareTo()方法
//为了更方便的直接比较,可以在接口后面指定类型,这样重写方法的时候参数类型就可以是本类类型
@Override
public int compareTo(Element another) {
return n-another.n;
}
@Override
public String toString() {
return "Element{" +
"n=" + n +
'}';
}
}
class Element2{
private int age;
private String name;
public Element2() {
}
public Element2(int age, String name) {
this.age = age;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Element2{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
如果放入的键值部分元素比较规则单一,那么实现Comparable接口比较好。
如果放入的键值部分的元素变化规则多变或者多个,用Comparator构造器比较好。
使用集合工具类Collections中的sort()方法可以对 List集合进行排序。要求List集合中的元素必须实现Comparable接口,或者再传入一个构造器,如:Collections.sort(myList , myComparator ).
九、关于方法的重写
放在集合中的元素都最好重写equals( )方法。如果实现了compareTo( )方法可以不用重写equals( )方法。
放在利用哈希表完成存储的集合中的元素,要重写equals( )和hashCode( )方法。
放在会将元素排序的集合中的元素要实现Comparable接口,重写compareTo( )方法。或者创建集合时传入比较器。
如有错误,希望能批评指正,不胜感激。