JAVA入门学习笔记(1)-- Collection集合的基础知识

一个计算机专业的学生,记录一下学习中的知识点,若存在错误还请指正,也欢迎任何学习交流。

1. 集合的概念

集合,是java中的一种容器,可以用来存储多个数据。
在很多时候我们存储数据会使用到数组,但是数组的长度是固定的,集合的长度是可以改变的。同时,数组中往往只能存储同一个类型的元素,比如统一基本类型或者同一类对象;集合句数组相比不能存储基本数据类型(若想要存储则需要使用包装类),只能存储对象,但是其中存储的对象却不要求统一, 一个集合中可以存储不同的对象类型。

2. 集合的分类

注意:这里我只是列举这几个我已经学习的比较基础的集合:Vector集合、ArrayList集合、LinkedList集合、TreeSet集合、HashSet集合、LinkedHashSet集合。他们都是Collection接口的实现类,关于这些集合的实现类,我将在第4节分别介绍。
这几个集合中前三者具有类似的特性:他们都是有序的,他们都允许存储重复的元素,他们都有索引,可以直接用for循环遍历。因此Java将他们这些共性搞作写成了一个接口List接口
TreeSet集合和HashSet集合,包括继承了HashSet集合的LInkedHashSet集合也存在一些共同特性:他们不可以存储重复的元素,他们都没有索引,不能直接用for循环遍历。Java写了一个Set接口来定义他们的共性方法。

3. 对集合操作的接口

前面所讲的Set接口和List接口都继承自Collection接口,他们都是比较基础的对单列集合进行操作的接口,如下图所示:
collection的api文档说明
下面一个个讲讲,这些接口以及其中的方法。

3.1 Collection接口

定义了所有单列集合的共性方法,所有单列集合都可以使用这些共性方法。因为是共性部分所以他也是没有索引方法的。
一些常用对单列集合操作的方法:

//把给定的对象添加到集合中, 注意这里返回值是boolean类型,是判断添加是否成功
public boolean add(E e);

//去除给定元素,返回值同样是boolean类型,判断是否删除成功
public boolean remove(E e);

//判断当前集合时候包含给定的对象
public boolean contains(E e);

//判断当前集合是否为空
public boolean isEmpty();

//返回集合中元素的个数
public int size();

//把集合中的元素存储到数组中
public Object[] toArray();

//清楚集合中的所有元素,注意并不是删除集合
public void clear();

3.2 List接口

前面我们讲过,这个接口继承了Collection接口并且是有带索引的方法的,而且它的集合实现类都是可以存储重复元素的,但是要注意不要超出索引范围。
下面来讲讲此接口的常用方法:

//在index处添加元素element
public void add(int index, E element);

//移除指定位置的元素,并将被移除的元素作为返回值返回
public E remove(int index);

//获取指定位置的元素
public E get(int index);

//用元素element替换index处的元素,并将其作为返回值返回
public E set(int index, E element);

3.3 Set接口

Set接口,没有索引,因此这个接口中的大多数方法都是直接继承的Collection接口中的所以没有什么特别的操作方法。

4. 集合类

4.1 集合的实现类

4.1.1 ArrayList < E >

底层是数组结构,它的实现是不同步的,支持多线程的。作为Java的初学者,相信很多人都喜欢和我一样,一遇到需要使用集合的时候都会毫不犹豫的使用此集合。实际上在使用集合时,是不合理的。
下面讲讲此实现类中包含的一些常用方法方法:

boolean add(E e)   // 将指定的元素添加到此列表的尾部。 
void add(int index, E element)  //将指定的元素插入此列表中的指定位置。 
E get(int index)    //返回此列表中指定位置上的元素。
boolean remove(Object o)  //移除此列表中首次出现的指定元素(如果存在)。  
int size() //返回此列表中的元素数。 

很显然此类中没有什么独特的方法,都是直接对List进行了中的抽线方法实现重写。

4.1.2 LinkedList < E >

此集合的底层是一个链表,所以这个集合实现类中间添加了很多对首尾元素进行操作的方法,值得注意的是若是想要使用LinkedList类的特有方法,不要使用多态。另外这个集合也是不同步的
这里直接实现重写自List的方法就不再表述了,我们直接来说说特有方法:

//下面这两个方法其实是等效的,都是对集合的头部添加一个元素
public void addFirst(E e);
public void push(E e);

//在集合的尾部添加一个元素 它和add方法也是等效的
public void addlast(E e);

//去除头部元素,并将其作为返回值返回   
//下面这两个操作其实也是等效的
public E removeFirst();
public E pop(E e);  

//移除尾部元素并返回
public E removeLast()

//获取并返回集合中的第一个元素
public E getFirst();

//获取并返回集合中的最后一个元素
public E getLast();

这里只是展示了几个和我一样的初学者较为常用的方法,若有需要还请查阅API文档。

4.1.3 Vecotor< E >

这个类在jdk1.2之后就慢慢被替代了,由于他是同步的,现在使用不多也可以做一些了解。这里也不多赘述了,感兴趣的可以查看API文档。

4.1.4 HashSet < E >

前面也提到过这个实现类是实现了Set接口,它的底层是一个哈希表。它是无序的,特别是不能保证顺序是永恒不变的,同时它允许添加null,注意他也是实现不同步的
此集合所有的方法都是实现重写Set接口的,这里就不再次列出了。值得注意的是,若是想要使用HashSet来存储自定义类型的元素,需要重写自定义类型中的hashcode和equals方法。

  • 哈希表:就是数组+链表的组合。在jdk1.8之后又出现了数组+红*黑树的组合。

4.1.5 LinkedHashSet < E >

继承了HashSet类,但是此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。也就是说它是可以保证存储进来的元素的顺序的。它的方法都是继承的,没有特别的方法,有兴趣的可以查阅API文档。

4.2 Conllections工具类

此类是Collection集合的工具类,它里面的方法都是静态方法,可以直接调用。下面展示一下:

  
  //将所有指定元素添加到指定 collection 中。 
  static <T> boolean addAll(Collection<? super T> c, T... elements)  
  
  //使用默认方法打乱LIst集合
  void shuffle(list<?> list) 
  
  //按照默认规则排序,默认的为升序
  void sort(List<E> l);
  
  //要想自己指定排序方式可以用下面面的方法  
   void sort(List<E> list,comparetor <? super E >
   //值得注意的是:需要从写compatetor中的compare方法 
   int compare(T o1, T o2) ;  //若返回 o1 - o2 则为升序  o2 - o1 则为降序

5. 集合的遍历

因为Set接口下的集合都是没有索引,因此不能直接通过for循环进行遍历的。要想对数组进行遍历则需要使用特殊的方法。

5.1 迭代器

在java.util包下,有一个Iterator< E > 接口,我们可以通过实习此接口对Collection集合进行迭代遍历。下面简单的介绍一下此接口中的一些常用方法:

boolean hasNext(); //如果还有可以迭代的元素则返回True,判断是否还有下一个元素
E next();  //返回迭代的下一个元素,没有元素则出现异常   

注意:此接口和一般接口不同,需要用特殊的实现类调用方法,不要自己些实现类调用。Collection接口中有一个方法,iterator()。我们可以使用如下方式对构造迭代器:

Iterator<E>  it = x.iterator();  //x表示集合的实现类,it表示迭代器名  是一种多态写法

其使用步骤就是:1.先用上面提到代码去构造迭代器。2.用hasNext()判断集合中还有没下一个元素。3.使用next()方法取出集合中的元素。

5.2 增强for循环(jdk1.5+)

在jdk1.5之后,Java官方增强了for循环的能力。它的底层原理也是Iterator迭代器。注意,它只是用来遍历数组和集合,而不能对元素进行增删操作,在遍历过程中。
格式如下:

//for(集合/数组的数据类型 变量名 : 集合名 / 数组名){ }
//举一个遍历数组的例子
public class Demo {
   public static void main(String[] args]{
   		int[] arr = {1,2,3,4};
   		for(int a: arr){
   		    System.out.println(a);
   		}
   }	
}

* 附加知识点

下面先要讲述一些在学习集合时所需要掌握的一些知识点:包括数据结构,还有泛型,其他的一些小知识点也会在后面进补充。

1.数据结构

关于数据结构想必在数据结构的课堂上都有进行详细的讲解,这里知识简单的介绍一下他们一些特性。

1.1 栈

栈,也称为先进后出表,如下图所示为栈的结构。给这个结构做 个简单的比喻:

四辆车分别标号,A、B、C、D.。他们在没有误入胡同之前很显 然是这样排序的:ABCD,那么他们在进入胡同之后想要出来只能D车先倒车、CBA三车也按次序倒出来才能走出困境。若不考虑哪个傻子司机又重新把车开会胡同的情况,这个出来时的排序应为DCBA。

通过这个简单的例子很容易理解栈的先入后出特性。

图1 栈结构

1.2 队列

队列,也称先入先出表。这个也很好理解,先进去的元素先出来。举个简单的栗子:

大家在坐火车过安检的时候,都会对行李进行安检,很显然,先放入的行李会先从出口处出来。

1.3 数组

数组,这个对于计算机专业的学生来说应该再熟悉不过了,数组都是我们这些初学者存储数据时候第一个想到的数据集合。如下图所示,作为一个连续的数据集合,当我们创建数组的时候,计算机会给这个数组的第一个元素位一个地址值,后面的元素位就会在初始元素位的地址值的基础上每个加上1。若数组的第一个元素的地址为a,那么第二个数组很显然就是a+1。

图2 数组使用数组存储会使我们在查询的时候非常的快,但是想要删减其中的某个元素将会十分的麻烦。 比如你需要在数组A中添加一个元素,你需要再创建一个比 数组A长度多1的数组B在进行元素的添加,删除也类似,效率相对较低。

1.4 链表

如下图所示为一个链表,每一个存储单元都会又几个部分,分别为存储前一个存储单元的地址区,元素存储区,下一个存储单元的地址区。这样的设置可以使得,在增加和添加元素时速度是很高效的,不需要如数组一般又要重新创建和复制等操作,只需要对地址区的地址进行更改即可。但是这样的设置使得查询起来将会十分麻烦。
图3 链表

1.5 树

树的主要类型:N元树、平衡树、二叉树、二叉搜索树、AVL树、红黑树、2-3树(多叉树比二叉树有更多的关键字和子节点)。树通常结合了有序数组和链表两种数据结构的优点在树中查找数据项的速度和在有序数组中查找一样快,并且插入数据项和删除数 据项的速度也和链表一样。

1.5.1 二叉树

二叉树,指的是树中每个节点都只能有两个子节点。其中二叉查找树(也可称二叉排序树)指一个节点的左子节点的关键字值小于这个父节点,右子节点的关键字的值大于或等于这个父节点。平衡树则指的是二叉树的左孩子树和右孩子数的结构是相等的。下图所示即为普通二叉树。
图 4 二叉树

1.5.2 红黑树

和普通二叉树不同,他有如下约束条件:
1.节点是红色或黑色。
2.根节点是黑色。
3.每个叶子节点都是黑色的空节点(NIL节点)。
4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
图5 红黑树
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

2. 关于泛型

2.1 概念

  1. 是一种未知的数据类型,不知道用什么类型的时候就可以使用。
  2. 也可作为变量,来接收数据类型
  3. 在需要使用泛型的时候,但却没有填写泛型的时候,JAVA默认使用Object类型,当然这样做不安全,有时会引发多态。

2.2 定义和使用

在JAVA中,我们需要在类定义时使用泛型,则需要按以下格式:

public class test <E>{
  			E a;
    		E b;
   }

即在类名的后面加上<>,并在里面写上需要使用的泛型。但同时也需注意对类中所需要使用泛型的相关变量的类型改写成填入的泛型。

而对于需要定义含有泛型的方法,则需按如下格式:

public <E> void methon (E e){
          //方法体
}

值得注意的是,<>中可以填写你想填入的的任意字母作为泛型, 这里我们统一使用字母E。在上面所展示的格式中,你调用此方法时,在参数列表中传入的数据为什么类型,泛型就是什么类型。同样假如想要定义一个含有泛型的静态方法,只需在上面所展示的格式中public后面加一个static。

public static <E> void methon (E e){
		//方法体
}

若是想要在接口中使用泛型,则需要如下定义:

interface InterfaceA <E>{
  //抽象方法
}

想要使用含有泛型的接口,要么先定义接口实现类,并在实现接口时,指定接口的泛型,要么在创建对象时,接口是什么泛型,实现类就是用什么泛型。两者的区别就是在不同的阶段指定泛型而已,都是可以的。展示以下:

//在实现类时指定泛型 我们这里就直接指定String类型了
public class InterfaceAImpl1 implements InterfaceA <String>{
//类的方法
}  

//===========================//
//在创建对象时指定泛型
//先实现接口
public class InterfaceAImpl2<E> implements InerfaceA <E>{
// 类的方法
}
//在创建对象时 这里也同样拿String类放入
InterfaceAImpl2<String> ial2 = new InterfaceAImpl2<>();
//仔细看第二种方法是不是和我们在定义ArrayList集合的时候一样呢

2.3 泛型通配符

<?>中的?就是泛型的通配符,当年我们在传递值时,我们不知道用什么数据类型来接受数据时就可以使用, 注意不在做定义时使用。且泛型没有继承概念,当我们使用了通配符后默认接受的数据类型就是Object类型。

2.4 泛型的限定

当看到<? extends E> 的表述时,证明使用的泛型只能是E类型的子类/本身的类型。
当看到<? super E>的表述时,证明使用的泛型只能是E类型的父类/本身的类型。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值