java-深入篇-类集

类集定义

Java中的类集,其实指的是一种开发框架,是对一些已经实现好的数据结构所进行封装后的产物,也可以将其理解为一个动态的对象数组。我们常以这种方式来存储对象。它的主要接口有以下这些:

Collection

是存放一组单值的最大父接口,所谓的单值就是指集合中的每个元素都是一个具体的对象。它的定义如下:

//<E>为泛型数据,意指该集合内只能存放该类型的对象
//具体知识点,会在以后介绍
public interface Collection<E> extends Iterable<E>

Collection使用泛型的一个好处在于在操作时必须指定具体的操作类型,从而保证类集操作的安全性,可以避免发生ClassCastException(类型转换异常)。在Collection加入泛型是在JDK1.5之后才出现。
关于Collection,其主要方法有:

//求出集合的大小
int size();
//判断集合是否为空
boolean isEmpty();
//判断某一个对象o是否在集合中存在
boolean contains(Object o);
//实例化Iterator接口
Iterator<E> iterator();
//将一个集合变成一个数组
Object[] toArray();
//指定返回的对象数据类型
<T> T[] toArray(T[] a);
//向集合添加指定数据
boolean add(E e);
//从集合移出指定数据
boolean remove(Object o);
//判断一组对象是否在集合中存在
boolean containsAll(Collection<?> c);
//向集合添加一组指定数据
 boolean addAll(Collection<? extends E> c);
 //从集合移出一组指定数据
boolean removeAll(Collection<?> c);
保存指定的内容
boolean retainAll(Collection<?> c);
//清除集合中的左右元素
void clear();
//与指定的对象进行比较
boolean equals(Object o);
//获取对象的hashCode
int hashCode();

在早期的java开发中,大多提倡使用Collection,但随着java的进一步发展,为了让程序的开发及使用更加明确,所以更加提倡使用Collection的子接口进行开发。主要有如下几个:

List接口

List接口可以保存各个重复的对象内容,它的定义如下:

public interface List<E> extends Collection<E>

list接口在Collection的基础上扩充了更多的方法,但我们要使用List接口,则需要实例化一个子类,下面介绍List的常用实现类有哪些:

ArrayList

ArrayList是List的子类,可以直接通过多态实例化List接口,同时它也是使用比较普遍的一个子类。它内部封装了一个数组用来保存相应的数据。也正是由于这样,它能够高效率地利用索引访问任意位置,但是也因为它是数组结构,所以在添加、删除数据等操作上效率会变低,但尽管如此,它在开发中的使用频率还是相当高的。它的初始化方式如下:

Arraylist<E> List = new Arraylist<E>(1000);

对于ArrayList类,所需要掌握的方法不算多,但这些方法都必须掌握,记牢。因为这属于常用方法,并且有的时候也会和其他类混淆。如果没有足够的记忆,那在开发上就会翻跟头了。下面贴一个记忆代码,建议在自己的电脑上实现一遍,看着最后的运行结果来对比记忆:

public class Test1 {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        ArrayList<String> addList = new ArrayList<String>();
        System.out.println("增加数据:1");
        list.add("1");
        System.out.println("显示数据:"+list.get(0));
        System.out.println("移除第一位的数据");
        String removeString = list.remove(0);
        System.out.println("被移除的数据:"+removeString);
        System.out.println("list中有多少个数据:"+list.size());
        System.out.println("增加数据:2");
        list.add("2");
        System.out.println("增加数据:3");
        list.add("3");
        System.out.println("移除第一位数据");
        removeString = list.remove(0);
        System.out.println("被移除的数据:"+removeString);
        System.out.println("当前的第一位数据为:"+list.get(0));
        System.out.println("当前的数据有:");
        for (String str:list){
            System.out.println(str);
        }
        System.out.println("allList增加数据4");
        list.add("4");
        System.out.println("allList增加数据5");
        list.add("5");
        System.out.println("allList增加数据6");
        list.add("6");
        System.out.println("list添加allList的所有数据");
        list.addAll(addList);
        System.out.println("显示当前list的数据");
        for (String string : list) {
            System.out.println(string);
        }
        System.out.println("删除当前list的所有数据");
        list.removeAll(list);
        System.out.println("删除完成,当前list中还有"+list.size()+"个数据");
    }
}

LinkedList

我们在上面说到,ArrayList在插入,删除一个数据上操作效率不高,很大的原因在于数组在某个位置插入一个数时,需要进行很多的移位操作。比如我们要在数组的最后一位插入一个新的数据,那么就要把前面所有的数据往前移移位,为输入的数据腾出空间。如果这个数组内部的元素少的话,那还好,没多大问题。但如果里面的元素有几千上万个呢?那么移除数据的时间可就要耗费很多了。那么,这个和LinkedList有什么关系吗?当然有,LinkedList虽然也继承于List接口,但它在执行插入和删除操作时比ArrayList更加高效,因为它是基于链表而进行封装的。这也决定了它在随机访问方面不如ArrayList,可以说,两者各有千秋,至于怎么使用,就要根据具体的情形而决定了。LinkedList的定义如下:

public class LinkedList<E>
   extends AbstractSequentialList<E>
   implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
···
}

而关于LinkList的使用,这里列出大致的主要方法,可以参照上面的例子自己写一个理解代码进行理解:

【1】add(数据)         添加数据
【2】add(int index, 数据)  在指定位置插入数据
【3】addFirst(数据)    在首位插入数据
【4】addLast(数据)         在末尾插入数据
【5】getFirst()              返回首位数据
【6】getLast()           返回末尾数据
【7】removeFirst()       移除并返回首位数据    
【8】removeLast()    移除并返回末尾数据
【9】FIFO 操作 First In First Out,队列操作 Queue
            操作          相当于
            ---------------------------------
            offer(数据)       - addLast  
            peek()          - getFirst
            poll()          - removeFist    

【10】LIFO 操作 Last In Fisrt Out,栈操作 Stack
            操作          相当于
            ---------------------------------
            push()          - addFirst
            pop()           - removeFirst

【11】get(int index)      - 获得指定位置的值
【12】contains(数据)            - 判断列表中是否存在与指定数据相等(equals)的值
【13】set(int index, 数据)          - 将指定位置设置为新的值
【14】size()                      - 获得列表中数据的数量
【15】remove(int index)   - 移除指定位置的元素,并返回被移除的数据
【16】remove(数据)              - 移除与指定数据相等的数据
【17】clear()                     - 清空
【18】iterator()              - 获得新建的迭代器实例

Vector

关于这个Vector类,是一个比较好玩的类。它出现于JDK1.0版本,也就是一个元老级的类。到了JDK1.2之后,开始重点强调集合框架的概念,所以先后也定义了很多的接口。但是因为新的接口还没有被人熟悉,而很多的开发者也习惯了Vector的存在,于是,为了让开发者能够顺利过渡。设计者让Vector多实现了List的接口,让它作为List的子接口而保存下来。当然,这并不是说这个类不好,与ArrayList相比,它的最大好处就是线程安全,而它的不足也很明显,就是使用同步处理的方式,在树立数据上性能比较低。所以到现在,很多地方也只是将其作为面试的一个简单问题,而不要求你去掌握它。由此也导致这个类慢慢淡出人们的关注了。或许这真的就是一个过渡阶段的结束了吧。也因为这样,这里不对Vector做详细的介绍。但它和ArrayList的比较需要记住,这也是一个面试点来的。

Set接口

讲完了List接口,下面再来介绍Set接口。Set接口也是Collection的子接口,但与List不同的是,Set接口中不能加入重复的元素。它的接口定义如下:

public interface Set<E> extends Collection<E>

关于Set接口,它的常用子类如下:

HashSet

hashSet内部封装着一个HashMap,数据作为键的存在,放在hashMap中。至于什么是HashMap,我们会在下面进行介绍,此处先不讲。用HashSet封装的数据是不重复并且无序排列的。
我们来是实现一个HashSet:

public class Test1 {

    public static void main(String[] args) {
        HashSet<Integer> hashSet = new HashSet<Integer>();
        hashSet.add(1);
        hashSet.add(12);
        hashSet.add(2);
        hashSet.add(2);
        hashSet.add(11);
        hashSet.add(3);
        hashSet.add(40);
        System.out.println(hashSet);
    }
}

运行结果为:

[1, 2, 3, 40, 11, 12]

从运行的结果来看,HashSet对于重复出现的数字2,是指增加一次,而且运行时往程序里添加数据的顺序也不等于集合中的保存顺序。由此可见,HashSet内的数组不可重复,且添加方式为无序添加。

TreeSet

hashSet内部封装着一个TreeMap,数据作为键的存在,放在TreeMap中。它和HashSet的不同之处在于TreeSet内存储的数组是按有小到大的顺序来排列的:

public class Test1 {

    public static void main(String[] args) {
        TreeSet<Integer> treeSet = new TreeSet<Integer>();
        treeSet.add(1);
        treeSet.add(12);
        treeSet.add(2);
        treeSet.add(2);
        treeSet.add(11);
        treeSet.add(3);
        treeSet.add(40);
        System.out.println(treeSet);

    }
}

运行结果为:

[1, 2, 3, 11, 12, 40]

可见,同样的数据,TreeSet最后展现的是从小到大排列,而HahSet则不然。

Map

我们前面讲了说到HashMap和TreeMap,其实它们不是和Collection在同一阵营的,而处于Map接口中。
和Collection的单值操作不同,Map每次操作的是一对对象,又叫二元偶对象,Map接口中的每个元素都是以键值对的形式存储在集合中。它的Map接口的定义如下:

public interface Map<K,V>

同时,Map预定义的方法如下:

//取出集合的长度
int size();
//判断集合是否为空
boolean isEmpty();
//判断制定的key是否存在
boolean containsKey(Object key);
//判断制定的value是否存在
boolean containsValue(Object value);
//根据key取出对应的value
V get(Object key);
向集合中加入元素
V put(K key, V value);
根据key删除value
V remove(Object key);
//将一个map集合的所有元素添加到另一集合中
void putAll(Map<? extends K, ? extends V> m);
//清楚集合的所有元素
void clear();
//取得所有的key
Set<K> keySet();
//取出全部的values
Collection<V> values();
将Map集合转化为Set集合
Set<Map.Entry<K, V>> entrySet();

注意的是,这里用的key-value形式的存储方法与之前介绍的不同,下面针对这个简单介绍一下:我们在之前所认识的数据存储方式,都是单值存储。比如数组,就是在每个数组里面存储一个元素,然后利用索引来寻找元素。基本上长得是这个样:
这里写图片描述
那么,这样的存储方式有什么问题吗?是的,单值存放依赖于索引,不论是链表还是数据,都对索引或者指针的依赖性比较强。同时,他们是一组步步相连的数组结构,要找到某个元素,就需要先找到一个入口索引,根据索引间的关系一步步去找到这个元素,从而进行操作。这样对于大数据量的存储来说并不合适。于是,就有了key-value,也即键值对。
键值对的存储方式比较简单,我们通过标签key来标志数据,通过这个这个key去关联,或者说映射value。也即是说,一但我们定义了一个键值对,那么就可以通过键值对中的key来直接对应存放的value,从而减低了对索引的依赖。图解大概如下(按自身的理解所做,如有不对,请指正):
这里写图片描述

另外,在方法

Set<Map.Entry<K, V>> entrySet();

中我们会看到一个Map.Entry,这是Map内部定义的一个Entry接口,是专门用来保存key-value的内容。它的定义如下:

interface Entry<K,V> {

}

当然,在它的内部也有如下几个方法:

K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();

方法比较最简单,这里不介绍有什么作用,希望可以根据前面的介绍去想一下分别有什么作用,对自己的记忆有帮助。
在一般的Map操作(增加或者取出数据)中,我们不用去关心Map.Entry接口,但是在将Map接口中的数据全部取出时就必须使用Map.Entry。

与之前的Collection类似,要实现Map接口也必须要将其实例化。它的重要子类如下:

HashMap

HashMap本身就是Map的子类,直接使用这个类来为Map进行实例化即可。它的定义如下:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    }

关于HashMap,我们从它存放的键值对数据就可以知道,它可以用来快速地查找数据,在这里要注意的是:在HashMap中,必须要注意以下几点:

1.key允许为null
2.key不允许重复
3.必须重写hashCode()和equals(),如果equals相等,hashCode()必须相同。如果equals()不相等,hashCode()尽量不相同。

它的主要方法如下:

 【1】put(k, v)           放入键值对数据
 【2】get(k)              用键获得对应的值
 【3】remove(k)           移除指定的键和它的值
 【4】containsKey(key)        是否包含指定的键
 【5】containsValue(value)    是否包含指定的值
 【6】size()          有多少对数据
 【7】clear()         清空
 【8】keySet()            获得一个 Set 类型集合,包含所有的键
 【9】entrySet()      获得一个 Set 类型结合,包含所有 Entry
 【10】values()           获得集合,包含所有的值

HashTable

关于这个类,同Vector一样,是一个过渡类型的类。也是在面试中偶尔会闻到的一个问题。它和HashMap的区别就在于:

1.HashMap采用异步处理方式,性能高。HashTable采用同步处理方式,性能低
2.HashMap是线程非安全的,HashTable则是线程安全的
3.HashMap的key允许为null,HashTable不允许

TreeMap

之前的两个类HashMap和HashTable都是没有对数据进行排序的。下面来介绍一个可以按Key排序的类TreeMap,它有两种实例方式:

TreeMap map = new TreeMap();
TreeMap map = new TreeMap(Comparator comparator);

第一种方式需要让key类实现Comparatable接口(String内部已经封装,作揖不需要)
第二种方式为插入一个已经实现了的Comparator接口,用来排序
它的主要方法同HashMap一致。

关于类集,在这里列举的只是一些常用的接口和实现类。同时这一部分也是面试中经常被问到的一部分。而HashMap部分的知识点是最为容易被问到的。所以更要掌握。至于剩下的那些没有列举出来的,建议抽时间去浏览一遍。虽然不要求都精通,但至少也知道大概才可以嘛。
好了,关于这一章的内容介绍到这。下一章:java-深入篇-I/O操作。敬请期待。

如对文章内容有疑惑,欢迎在下方留言探讨补充。谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值